最近开始学习算法,在这里记录心得体会。今日体会是,用数学语言描述算法,会比较清晰。
/* 理解快慢指针:
* 1. 给链表的所有节点一个编号: 长度为len的链表, 节点编号依次为:
* 0,1,...,len-1
* 2. 关于中点:
* 2.1. 如果链表长度为0, 上述编号失效, 因为len-1=-1没有意义;
* 2.2. 如果链表长度为奇数2n+1, 那么中点为n;
* 2.3. 如果链表长度为偶数2n, 那么中点为(2n-1)/2=n-0.5,
* 根据需要选择n-1或n;
* 3. 快慢指针的移动:
* 3.1. slow: 慢指针每次移动1步
* 3.2. fast: 快指针每次移动2步
* 4. 根据需要推导算法:
* 4.0. 基本思路:
* 快指针主动移动, 慢指针跟随移动;
* 结束时, 快指针移动2n, 慢指针移动n;
* 根据起点不同控制慢指针结束时所在位置;
* 4.1. 需求1: 偶数时指向中间靠右, 中点选择n而不是n-1
* slow: 从0开始, 结束时为n
* fast: 从0开始, 结束时为2n
* 如果len=2n, 则慢指针指向右侧(选择了n而不是n-1);
* 如果len=2n+1, 则慢指针指向中间, n=(0+2n)/2为中点;
* 4.2. 需求2: 偶数时指向中间靠左, 中点选择n-1而不是n
* 思路: slow慢一步, 或者fast快一步;
* 写代码时让fast快一步更好实现
* slow: 从0开始, 结束时为n
* fast: 从1开始, 结束时为2n+1; (如果长度小于2则提前结束)
* 如果len=2n+1, 则n为中点;
* 如果长度为2n+2, 因为fast先走了一步, 所以只会移动2n步,
* 则slow=n为左侧点(0+2n+1)/2=n+0.5, 左侧为n, 右侧为n+1
*/
node_t*
get_mid(node_t *list, int sel_left)
{
if (!list)
return (list);
node_t *fast = sel_left ? list->next : list, *slow = list;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
return (slow);
}
/*
* 回文判断思路:
* 1. 如果链表长度为奇数, 中点不需要判断;
* 如果链表长度为偶数, 则要求左右对称
* 2. 步骤:
* 2.1. 找到并记录链表中点位置: 奇数长度返回中点, 偶数长度返回左侧点;
* 2.2. 把中点右侧链表反转;
* 2.3. 左右链表按照顺序比较各个节点, 右侧链表长度小于或等于左侧,
* 遇到NULL则说明相等;
* 2.4. 在比较的同时, 完成链表的再次反转;
* 2.5. 重新拼接链表.
*/
static inline void
move_node(node_t **pdst, node_t **psrc)
{
node_t *next = (*psrc)->next;
(*psrc)->next = *pdst;
*pdst = *psrc;
*psrc = next;
}
int
is_parlindrome(node_t *list)
{
// 空链表认为是回文
if (!list)
return (1);
// 找到链表中点或中点偏左
node_t *fast = list->next, *mid = list;
while (fast && fast->next) {
mid = mid->next;
fast = fast->next->next;
}
// 断开右侧链表
node_t *tmp = mid->next;
mid->next = NULL;
// 反转右侧链表
node_t *right = NULL;
while (tmp)
move_node(&right, &tmp);
// 比较左右链表, 同时再次反转右侧链表, 右侧链表长度一定不大于左侧
node_t *left = list;
tmp = right;
right = NULL;
int yes = 1;
while (tmp && yes) {
yes = tmp->value == left->value;
left = left->next;
move_node(&right, &tmp);
}
while (tmp)
move_node(&right, &tmp);
// 重新拼接链表
mid->next = right;
return (yes);
}