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
文章介绍了云计算的历史和演进过程,主要分为如下几块内容:
- 时间共享主机。在19世纪50年代到80年代期间,计算机很昂贵,多个用户共用一台计算机很普遍,那时主要是通过时间片轮转的方式共享,类似于现在单核cpu对多线程的调度。https://en.wikipedia.org/wiki/Time-sharing
- 互联网。19世纪50年代末出现局域网,直到90年代,Internet才诞生。互联网的诞生催生了大量的网络应用,也激发了对服务器和数据中心的大量需求。
- 虚拟机。虚拟机的概念其实很早就出现了,1966年IBM就在操作系统中引入了虚拟机。2013年诞生的docker,是一种操作系统级别的虚拟化产品,它的出现使得微服务架构蓬勃发展。
- SaaS, PaaS, IaaS
- 真正的云。亚马逊EC2、微软Azure Virtual Machines、谷歌Google Compute Engine
- 超越。
- 商业视角。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