ARTS打卡第十周

博客包含三部分内容。算法部分探讨LeetCode旋转链表题的两种解法;Review介绍云计算历史,涵盖时间共享主机、互联网、虚拟机等发展阶段;Tip讲解使用MySQL官方Docker镜像初始化数据库的方法,将初始化文件放指定目录,容器启动时执行,且只执行一次。

Algorithm:61. Rotate List

https://leetcode-cn.com/problems/rotate-list/

Given a linked list, rotate the list to the right by k places, where k is non-negative.

Example 1:

Input: 1->2->3->4->5->NULL, k = 2
Output: 4->5->1->2->3->NULL

Explanation:

rotate 1 steps to the right: 5->1->2->3->4->NULL
rotate 2 steps to the right: 4->5->1->2->3->NULL

Example 2:

Input: 0->1->2->NULL, k = 4
Output: 2->0->1->NULL

Explanation:

rotate 1 steps to the right: 2->0->1->NULL
rotate 2 steps to the right: 1->2->0->NULL
rotate 3 steps to the right: 0->1->2->NULL
rotate 4 steps to the right: 2->0->1->NULL

分析:链表问题一般有两种思路,一是先求出链表长度,然后利用长度计算下标,类似于数组操作;另一种是用双指针,可以是快慢指针,也可以是滑动窗口,其思想是把下标关系转换成两个指针之间的距离,然后同时前进。
下面也用这两种思路来解答这一题。有点特别的是,当k大于数组长度时,双指针解法仍然要计算数组长度,否则,会很低效。
这两种解法时间复杂度都是O(n),空间复杂度都是O(1)。当k小于等于链表长度时,解法二应该是优于解法一的,因为只需要遍历一遍;当k大于链表长度时,两种解法是差不多的,甚至解法一可能更快一点,因为解法二中多了一个指针需要移动。从Leetcode的提交记录来看,解法一更快一点,且解法一思路更清晰一些,所以个人倾向于解法一。当然了,解法二也可以像解法一一样,先求出链表长度,然后取模,然后再利用双指针移动,但感觉有点多次一举了。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    // 解法二:双指针。当k小于等于链表长度时,遍历一遍搞定;当k大于链表长度时,第一次遍历仍然要计算出链表长度然后将k取模运算,否则当k远远大于链表长度时,快指针会做很多次无效移动。
    public ListNode rotateRight(ListNode head, int k) {
        if(head == null || head.next == null || k == 0)
            return head;
        
        ListNode p = head;
        ListNode q = head;
        int i;
        for(i=1; i<= k && p.next != null; i++, p=p.next);
        
        if(i <= k) { // i+1 is length of list
            k %= i;
            p = head;
            for(i=0; i<k; i++, p=p.next);
        }
        
        if(p == q)
            return head;
        
        while(p.next != null) {
            p = p.next;
            q = q.next;
        }
        
        ListNode temp = q.next;
        q.next = null;
        p.next = head;
        head = temp;
        
        return head;
    }
    
    // 解法一:先求出长度,再计算需要断开的位置,将后半段插到前面去,返回新的链表头
    private ListNode solutaion_1(ListNode head, int k) {
        if(head == null || k == 0)
            return head;
        
        ListNode p = head;
        int size = 1;
        while(p.next != null) {
            p = p.next;
            size++;
        }
        // now p is point to the end
        
        k %= size;
        if(k == 0)
            return head;
        
        ListNode q = head;
        for(int i=1; i< size-k; i++, q=q.next);
        // now q is point to the k+1 from the end
        
        ListNode temp = q.next;
        q.next = null;
        p.next = head;
        head = temp;
        
        return head;        
    }
}

Review: Cloud Computing History

https://medium.com/pgs-software/cloud-computing-history-7c2c188a30a
文章介绍了云计算的历史和演进过程,主要分为如下几块内容:

  1. 时间共享主机。在19世纪50年代到80年代期间,计算机很昂贵,多个用户共用一台计算机很普遍,那时主要是通过时间片轮转的方式共享,类似于现在单核cpu对多线程的调度。https://en.wikipedia.org/wiki/Time-sharing
  2. 互联网。19世纪50年代末出现局域网,直到90年代,Internet才诞生。互联网的诞生催生了大量的网络应用,也激发了对服务器和数据中心的大量需求。
  3. 虚拟机。虚拟机的概念其实很早就出现了,1966年IBM就在操作系统中引入了虚拟机。2013年诞生的docker,是一种操作系统级别的虚拟化产品,它的出现使得微服务架构蓬勃发展。
  4. SaaS, PaaS, IaaS
  5. 真正的云。亚马逊EC2、微软Azure Virtual Machines、谷歌Google Compute Engine
  6. 超越。
  7. 商业视角。Businesses that are able to keep up with these changes stand to continually gain the most benefits compared to those who sit idly by.与无所事事的人相比,能够跟上变化的企业将持续获得最大收益。

Tip:使用MySQL的官方Docker镜像如何初始化数据库

我使用的是官方mariadb基础镜像mariadb:latest ,我们只要把初始化数据库的sql文件放到 /docker-entrypoint-initdb.d/ 目录下,容器在启动的时候就会执行这个sql。除了sql文件,同样也支持sh脚本和sql.gz文件。

有一点需要注意,这个初始化工作只会执行一次,一旦数据库中已经有数据了,就不会再次初始化。

上述初始化工作是在基础镜像的 docker-entrypoint.sh 脚本里做的,这里面会判断在mysql的数据目录下是否有 /mysql 子目录,如果有的话,就不执行初始化的逻辑:

        ...
        DATADIR="$(_get_config 'datadir' "$@")"
        if [ ! -d "$DATADIR/mysql" ]; then
        	...
            for f in /docker-entrypoint-initdb.d/*; do
                    case "$f" in
                            *.sh)     echo "$0: running $f"; . "$f" ;;
                            *.sql)    echo "$0: running $f"; "${mysql[@]}" < "$f"; echo ;;
                            *.sql.gz) echo "$0: running $f"; gunzip -c "$f" | "${mysql[@]}"; echo ;;
                            *)        echo "$0: ignoring $f" ;;
                    esac
                    echo
            done        	

Share:

### ARTS打卡 Java 学习或项目进展 #### 一、Algorithm 算法练习 在算法方面,最近研究了回文验证问题中的双指针方法。通过实现 `validPalindrome` 函数来判断给定字符串是否可以通过删除最多一个字符形成回文串[^2]。 ```cpp class Solution { public: bool validPalindrome(string s) { int i = 0; int j = s.size() - 1; int diffCount = 0; while (i < j) { if (s[i] == s[j]) { ++i; --j; } else { if (diffCount > 0) return false; // 尝试移除左边或右边的一个字符并继续比较剩余部分 string sub1 = s.substr(i + 1, j - i); string sub2 = s.substr(i, j - i); return is_palindrome(sub1) || is_palindrome(sub2); } } return true; // 辅助函数用于检测子串是否为回文 auto is_palindrome = [](const std::string& str){ int l = 0, r = str.length() - 1; while(l<r && str[l]==str[r]){ ++l;--r; } return l>=r; }; } }; ``` 此版本改进了原始逻辑,在遇到不匹配的情况时不再直接修改原字符串而是创建两个新的子串分别测试其合法性,从而提高了代码可读性和效率。 #### Review 技术文章阅读心得 关于数据库操作的学习笔记中提到 MySQL 支持四种不同的事务隔离级别:未提交读(Read Uncommitted),已提交读(Read Committed),可重复读(Repeatable Read),序列化(Serializable)[^1]。每种级别的特性决定了并发环境下数据的一致性程度以及性能表现之间的权衡关系。 另外还探讨了两种常见的锁机制——悲观锁(Pessimistic Locking) 和乐观锁(Optimistic Locking) 的原理及其适用场景: - **悲观锁** 假设冲突不可避免,因此总是先锁定资源再执行更新动作; - **乐观锁** 则认为大多数情况下不会发生竞争,仅当实际发生写入时才检查是否有其他更改影响到目标对象。 这两种策略各有优劣,具体选择取决于应用的具体需求和环境特点。 #### Tip 技巧总结 对于上述提及的内容,建议开发者们理解各自系统的默认配置,并根据业务逻辑调整合适的参数设置;同时也要熟悉如何利用编程语言提供的工具去处理并发控制问题,比如 Java 中可以借助框架如 Spring 提供的支持简化分布式事务管理过程。 #### Share 经验交流 分享过程中发现很多同学对多线程下的共享变量可见性和原子性的概念存在误解。实际上,Java 内存模型规定了 volatile 关键字能保证变量的即时可见性但不具备原子性保障,而 synchronized 或者 ReentrantLock 可以提供更强大的同步功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值