(1) LeetCode 的链表默认是没有头结点的,为了方便处理,在做题的时候习惯加上头结点。其实现方法如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
// 题目所给“头结点”记为“head”
...
struct ListNode *dummy = malloc(sizeof(struct ListNode));
dummy->next = head;
...
(2) 申请链表的一个结点都需要将指针初始化,否则会出现类似错误:
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
个人思路:
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
if(!l1 || !l2){
return l1 == NULL ? l2 : l1;
}
struct ListNode *dummy = malloc(sizeof(struct ListNode)), *p = dummy;
dummy->next = NULL;
while(l1 || l2){
if(l1 == NULL){
p->next = l2;
break;
}
if(l2 == NULL){
p->next = l1;
break;
}
if(l1->val < l2->val){
p->next = l1;
p = p->next;
l1 = l1->next;
} else {
p->next = l2;
p = p->next;
l2 = l2->next;
}
}
return dummy->next;
}
guanpengchn 大佬的思路:
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
if(!l1 || !l2){
return l1 == NULL ? l2 : l1;
}
if(l1->val <= l2->val){
l1->next = mergeTwoLists(l1->next, l2);
return l1;
} else {
l2->next = mergeTwoLists(l1, l2->next);
return l2;
}
}
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
给定1->2->3->4
, 你应该返回2->1->4->3
.
思路:
struct ListNode* swapPairs(struct ListNode* head){
if(!head || !head->next)
return head;
struct ListNode *dummy = malloc(sizeof(struct ListNode));
dummy->next = head;
struct ListNode *pre = dummy;
while(pre->next && pre->next->next){
struct ListNode *l1 = pre->next, *l2 = pre->next->next;
l1->next = l2->next;
l2->next = pre->next;
pre->next = l2;
pre = l1;
}
return dummy->next;
}
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
输入:1->1->2
输出:1->2
输入:1->1->2->3->3
输出:1->2->3
思路:
p
遍历链表,遇到下一个结点值与当前结点相同,即p->val == p->next->val
时,就将下一个结点“删除”:p->next = p->next->next;
struct ListNode* deleteDuplicates(struct ListNode* head){
if(!head || !head->next)
return head;
struct ListNode *p = head;
while(p->next){
if(p->val == p->next->val){
p->next = p->next->next;
} else {
p = p->next;
}
}
return head;
}
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。
输入:1->2->3->3->4->4->5
输出:1->2->5
输入:1->1->1->2->3
输出:2->3
pre
指向最后一个已经处理好的结点,cur
遍历后续结点
cur
遇到重复值 x
时,pre
不动,cur
移到最后一个重复的x
处。此时令 pre->next = cur->next
即可删除值为x
的重复结点pre
与 cur
同步移动pre
遍历完所有结点(处理结束)struct ListNode* deleteDuplicates(struct ListNode* head){
if(!head || !head->next)
return head;
struct ListNode *dummy = malloc(sizeof(struct ListNode)), *cur = head, *pre = dummy;
dummy->next = head;
while(pre && pre->next){
cur = pre->next;
// 如果cur到最后一位了或者当前cur所指元素没有重复值
if(!cur->next || cur->next->val != cur->val)
pre = cur;
else {
// 将cur定位到一串重复元素的最后一位
while(cur->next && cur->next->val == cur->val)
cur = cur->next;
// pre->next跳过中间所有的重复元素
pre->next = cur->next;
}
}
return dummy->next;
}
给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。
输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL
思路: 这道题说是链表的循环旋转,本质上是将倒数第K个元素作为头,原来的头接到原来的尾上
//链表长度计算
int getLength(struct ListNode* head){
struct ListNode* p = head;
int len = 1;
while(p->next){
p = p->next;
++len;
}
return len;
}
struct ListNode* rotateRight(struct ListNode* head, int k){
if(!head || !head->next){
return head;
}
int len = getLength(head);
int gap = len - k % len; //gap为倒数第几个,从gap处断开,方便后续拼接
struct ListNode *dummy = malloc(sizeof(struct ListNode)), *p = head, *q = p;
dummy->next = head;
for(int i = 1; i < gap; i++){
p = p->next; //找到倒数第k个的前一个结点,以便于修改操作
}
while(q->next){
q = q->next; // 走到表尾
}
q->next = dummy->next; //表尾接到原表头
dummy->next = p->next; //原表头变为倒数第k个结点
p->next = NULL; //最后元素指空
return dummy->next;
}
给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
输入: head = 1->4->3->2->5->2, x = 3
输出: 1->2->2->4->3->5
思路:
big
、small
,分别记录结点值 val
比 x 大的和比 x 小的结点big
链表的末尾置空,将big
链接在small
的后面struct ListNode* partition(struct ListNode* head, int x){
struct ListNode* smallLink = malloc(sizeof(struct ListNode)), *small = smallLink;
struct ListNode* bigLink = malloc(sizeof(struct ListNode)), *big = bigLink;
smallLink->next = NULL; bigLink->next = NULL;
while(head != NULL) {
if(head->val < x){
small->next = head;
small = head;
} else {
big->next = head;
big = head;
}
head = head->next;
}
big->next = NULL;
small->next = bigLink->next;
return smallLink->next;
}
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明: 1 ≤ m ≤ n ≤ 链表长度。
输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
思路:
这道题我想到了一个思路:
start
、end
、preStart
、rearEnd
分别指向第 m m m、 n n n 、 m − 1 m - 1 m−1、 n + 1 n + 1 n+1 个结点struct ListNode* t = NULL;
struct ListNode* reverse(struct ListNode* head) {
struct ListNode *L = malloc(sizeof(struct ListNode)), *p;
L->next = NULL;
while(head != NULL){
p = head->next;
head->next = L->next;
L->next = head;
head = p;
}
return L->next;
}
struct ListNode* reverseBetween(struct ListNode* head, int m, int n){
if(!head || m > n)
return head;
struct ListNode *H = malloc(sizeof(struct ListNode)); //自己申请的头结点
H->next = head;
struct ListNode *start = H, *end = H, *preStart = start, *rearEnd = end;
int i = 0;
while(i < n) {
if(i < m) {
preStart = start;
start = start->next;
}
end = end->next;
i++;
}
rearEnd = end->next;
end->next = NULL;
t = start;
preStart->next = reverse(start); //反转链表m~n
t->next = rearEnd; //接上n之后的链表
return H->next;
}
虽然这样做很直观,但是不管是代码量还是对运行内存来说都是比较大的。
大佬的思路:
实现思路 : 以1->2->3->4->5, m = 2, n=4 为例:
· 定位到要反转部分的头节点 2,head = 2;其前驱结点为 1,pre = 1;
· 当前节点的下一个节点 3 调整为前驱节点的下一个节点 1->3->2->4->5,
· 当前结点仍为 2, 前驱结点依然是 1,重复上一步操作…
· 1->4->3->2->5.
struct ListNode* reverseBetween(struct ListNode* head, int m, int n){
//添加头结点
struct ListNode* dummy = malloc(sizeof(struct ListNode));
dummy->next = head;
//pre 作为指向第 m - 1 个结点的指针
//nxt 遍历 pre 后面第 m 到 n 个需要被交换的结点
struct ListNode* pre = dummy, *nxt = NULL;
for(int i = 1; i < m; i++)
pre = pre->next;
head = pre->next; //pre 到达目的地 m - 1,head 指向第 m 个结点
//循环反转第 m 至 n 个结点
for(int i = m; i < n; i++){
//交换head与head->next
nxt = head->next;
head->next = nxt->next;
nxt->next = pre->next;
pre->next = nxt;
}
return dummy->next;
}
不得不说一题多解思路的重要性,反观我的代码运行时内存消耗:7 MB,而大佬思路的内存消耗只有 5.6 MB。
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
输入: head = [3,2,0,-4], pos = 1
输出: true
解释: 链表中有一个环,其尾部连接到第二个节点。
这道题在两年前做过,但是不会做,最后还是参考了官方答案才明白其含义
思路:
bool hasCycle(struct ListNode *head) {
if(!head)
return false;
struct ListNode *slow = head, *fast = head;
while(slow->next != NULL && fast->next != NULL){
if(slow->next == fast->next->next)
return true; //快慢指针相遇,则存在环
slow = slow->next;
fast = fast->next->next;
if(slow == NULL || fast == NULL)
return false;
}
return false;
}
注:若链表是单链,该思路同样可用,如: 876. 链表的中间结点。
思路一:
王道数据结构里的思路:从链表相交处开始计算,到链表结尾,这段长度对l1和l2是相等的。我们就可以先让长链表指针多走这么几步,然后长短链表同步走,直到检测到l1 == l2。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
if (headA == NULL || headB == NULL) {
return NULL;
}
int lenA = getLength(headA), lenB = getLength(headB);
while (lenA > lenB) {
headA = headA->next;
--lenA;
}
while (lenB > lenA) {
headB = headB->next;
--lenB;
}
while (headA != NULL) {
if (headA == headB) {
return headA;
}
headA = headA->next;
headB = headB->next;
}
return NULL;
}
int getLength(struct ListNode *head) {
int count = 1;
while (head != NULL) {
head = head->next;
count++;
}
return count;
}
类似思路题目:#面试题22. 链表中倒数第 k 个结点
思路二:
参考图解相交链表
设 A:a + c,B:b + c,其中 c 为尾部公共部分长度。将两个链表连起来,A->B和B->A,长度:a + c + (b + c) = b + c + (a + c) => (a + c + b) + c = (b + c + a) + c,
若链表AB相交,则a + c + b与b + c + a就会抵消,则会在公共处c起点相遇;
若不相交,a + b = b + a 。因此相遇处是NULL。
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
if(!headA || !headB)
return NULL;
struct ListNode *pA = headA, *pB = headB;
while(pA != pB){
pA = (pA != NULL ? pA->next : headB);
pB = (pB != NULL ? pB->next : headA);
}
return pA;
}
思路:
bool isPalindrome(struct ListNode* head){
if(!head || !head->next)
return true;
struct ListNode *fast = head, *slow = head, *L = malloc(sizeof(struct ListNode));
L->next = NULL;
while(fast && fast->next) {
//找中点
slow = slow->next;
fast = fast->next->next;
//同时对前面进行反转(头插法)
head->next = L->next;
L->next = head;
head = slow;
}
//slow指向后半部分头结点,head指向前半部分头结点
if(fast)
slow = slow->next;
head = L->next;
//前半部分和后半部分进行比较
while(head){
if(head->val != slow->val)
return false;
head = head->next;
slow = slow->next;
}
return true;
}
解题思路
struct ListNode* oddEvenList(struct ListNode* head){
if (head == NULL) return NULL;
struct ListNode* odd = head, *even = head->next, *evenHead = even;
while (even != NULL && even->next != NULL) {
odd->next = even->next;
odd = odd->next;
even->next = odd->next;
even = even->next;
}
odd->next = evenHead;
return head;
}
给你一个单链表的引用结点
head
。链表中每个结点的值不是 0 就是 1。已知此链表是一个整数数字的二进制表示形式。
请你返回该链表所表示数字的 十进制值 。
输入:head = [1,0,1]
输出:5
解释:二进制数 (101) 转化为十进制数 (5)
思路:
由于链表中从高位到低位存放了数字的二进制表示,因此我们可以使用二进制转十进制的方法,在遍历一遍链表的同时得到数字的十进制值。
个人理解为模拟计算机的运算:每次算术左移 前一位乘以2,再加上当前位,即为十进制数。
int getDecimalValue(struct ListNode* head){
struct ListNode* cur = head;
int ans = 0;
while (cur != NULL) {
ans = ans * 2 + cur->val;
cur = cur->next;
}
return ans;
}
给定一个单链表 L:L0→L1→…→Ln-1→Ln ,
将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
例如:给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3.
思路:
题目的要求里不难看出返回链表的特征:
通过观察,可以将重排链表分解为以下三个步骤:
简而言之:
//找到中间结点
struct ListNode* middleNode(struct ListNode* head){
struct ListNode* fast = head, *slow = head;
while(fast != NULL && fast->next != NULL){
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
//反转链表
struct ListNode* reverseList(struct ListNode* head){
if(!head || !head->next)
return head;
struct ListNode *L = malloc(sizeof(struct ListNode)), *p;
L->next = NULL;
while(head != NULL) {
p = head->next;
head->next = L->next;
L->next = head;
head = p;
}
return L->next;
}
//合并两半部分链表
void mergeList(struct ListNode* left, struct ListNode* right){
struct ListNode *leftTemp, *rightTemp;
while(right != NULL){
leftTemp = left->next;
rightTemp = right->next;
left->next = right;
right->next = leftTemp;
left = leftTemp;
right = rightTemp;
}
}
//功能实现函数
void reorderList(struct ListNode* head){
if(!head || !head->next)
return head;
struct ListNode *middle = middleNode(head), *left = head, *right = middle->next;
middle->next = NULL;
right = reverseList(right);
mergeList(left, right);
}
题目描述
给你链表的头结点
head
,请将其按 升序 排列并返回 排序后的链表 。
解题思路:归并
class Solution {
public ListNode sortList(ListNode head) {
// 1、递归结束条件
if (head == null || head.next == null) {
return head;
}
// 2、找到链表中间节点并断开链表 & 递归下探
ListNode midNode = middleNode(head);
ListNode rightHead = midNode.next;
midNode.next = null;
ListNode left = sortList(head);
ListNode right = sortList(rightHead);
// 3、当前层业务操作(合并有序链表)
return mergeTwoLists(left, right);
}
// 找到链表中间节点(876. 链表的中间结点)
private ListNode middleNode(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode slow = head;
ListNode fast = head.next.next;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
// 合并两个有序链表(21. 合并两个有序链表)
private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode sentry = new ListNode(-1);
ListNode curr = sentry;
while(l1 != null && l2 != null) {
if(l1.val < l2.val) {
curr.next = l1;
l1 = l1.next;
} else {
curr.next = l2;
l2 = l2.next;
}
curr = curr.next;
}
curr.next = l1 != null ? l1 : l2;
return sentry.next;
}
}
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
输入: (2 -> 4 -> 3) + (5 -> 6 -> 4)
输出: 7 -> 0 -> 8
原因: 342 + 465 = 807
解题思路
sum
,初始值为0,用来记录两链表在同一位置上的两数相加之和carry
,初始值为0,用来记录两链表在同一位置上的两数相加之和的进位数。每一位计算的同时需要考虑上一位的进位问题,而当前位计算结束后同样需要更新进位值dummy
以便操作链表/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2){
if(l1 == NULL)
return l2;
if(l2 == NULL)
return l1;
struct ListNode *dummy = malloc(sizeof(struct ListNode));
dummy->next = NULL; //这里很重要,定义了指针以后,要把它的next赋成NULL,否则会编译出错
struct ListNode *cur = dummy;
int sum = 0, carry = 0; //sum记录当前两个数相加结果,carry记录结果的进位数
while(l1 || l2){
int x = (l1 == NULL ? 0 : l1->val);
int y = (l2 == NULL ? 0 : l2->val);
sum = x + y + carry;
carry = sum / 10; //进位记录
sum %= 10; //取个位数,以建立新结点
cur->next = malloc(sizeof(struct ListNode));
cur->next->val = sum;
cur->next->next = NULL; //这里很重要,定义了指针以后,要把它的next赋成NULL,否则会编译出错
cur = cur->next;
if(l1)
l1 = l1->next;
if(l2)
l2 = l2->next;
}
// 检查最高位的进位
if(carry == 1){
cur->next = malloc(sizeof(struct ListNode));
cur->next->val = carry;
cur->next->next = NULL; //这里很重要,定义了指针以后,要把它的next赋成NULL,否则会编译出错
}
return dummy->next;
}
遇到的问题
Line 70: Char 15: runtime error: member access within misaligned address 0xbebebebebebebebe for type ‘struct ListNode’, which requires 8 byte alignment (ListNode.c)
0xbebebebebebebebe: note: pointer points here
...
//这里很重要,定义了指针以后,要把它的next赋成NULL,否则会编译出错
dummy->next = NULL;
...
cur->next->next = NULL;
...
注: 解决方法来源于博主「Eunhyuk_Z」的原创文章 —— LeetCode练习2–【链表】两数相加(中等)
解决方法就是上面代码中提到的很重要的地方,由于结构体内本身存在next
指针,而申请结构体空间后同时定义了next
指针,此时next
指针未指向任何空间,故在测试时可能导致上述错误,所以要定义新指针的next
为NULL
。
类似出现的问题: member access within misaligned address 0x000000000031 for type ‘struct ListNode’, which requires 8 byte alignment
解决方法:参考博主 沧海漂游_ 的文章
给你一个单链表的引用结点
head
。链表中每个结点的值不是0
就是1
。已知此链表是一个整数数字的二进制表示形式。
请你返回该链表所表示数字的 十进制值 。
示例 1:
输入: head = [1,0,1]
输出: 5
解释: 二进制数 (101) 转化为十进制数 (5)
- 链表不为空。
- 链表的结点总数不超过
30
。- 每个结点的值不是
0
就是1
。
解题思路
采用位运算,result << 1
就相当于 result * 2
, result |= 1(result |= 0)
相当于 result++
(不变)
注:
x & 0 = 0 (与运算,一假则假),x | 1 = 1(或运算,一真则真)
int getDecimalValue(struct ListNode* head){
struct ListNode* cur = head;
int ans = 0;
while (cur != NULL) {
ans = ans * 2 + cur->val;
cur = cur->next;
}
return ans;
}
总结
关于位运算的讲解:位运算(&、|、^、~、>>、<<)
位运算的思路对于一个能转化为处理二进制数较为方便的问题有较好的作用,如八进制、十六进制、十进制等需要转为二进制计算的题目。
题目描述
设计LRU缓存结构,该结构在构造时确定大小,假设大小为K,并有如下两个功能
- set(key, value):将记录(key, value)插入该结构
- get(key):返回key对应的value值
[要求]
- set和get方法的时间复杂度为O(1)
- 某个key的set或get操作一旦发生,认为这个key的记录成了最常使用的。
- 当缓存的大小超过K时,移除最不经常使用的记录,即set或get最久远的。
若opt=1,接下来两个整数x, y,表示set(x, y)
若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返>回-1
对于每个操作2,输出一个答案
import java.util.*;
public class Solution {
/**
* lru design
* @param operators int整型二维数组 the ops
* @param k int整型 the k
* @return int整型一维数组
*/
private Map<Integer, Node> map = new HashMap<>(); // map充当一张查找表,记录缓存元素
private Node head = new Node(-1, -1); //头指针
private Node tail = new Node(-1, -1); //尾指针
private int k; //参数k的拷贝,以便于该类中其他方法使用
public int[] LRU (int[][] operators, int k) {
// write code here
this.k = k;
head.next = tail;
tail.pre = head;
int len = (int)Arrays.stream(operators).filter(x -> x[0] == 2).count();
int[] res = new int[len];
for(int i = 0, j = 0; i < operators.length; i++){
if(operators[i][0] == 1){ //如果是1-->set
set(operators[i][1], operators[i][2]);
} else {
res[j++] = get(operators[i][1]);
}
}
return res;
}
private void set(int key, int val){
if(get(key) != -1){
map.get(k).val = val;
} else {
if(map.size() == k){
int rk = tail.pre.key;
tail.pre.pre.next = tail;
tail.pre = tail.pre.pre;
map.remove(rk);
}
Node node = new Node(key, val);
map.put(key, node);
moveToHead(node);
}
}
private int get(int key){
if(map.containsKey(key)){
Node node = map.get(key);
node.pre.next = node.next;
node.next.pre = node.pre;
moveToHead(node);
return node.val;
}
return -1;
}
private void moveToHead(Node node){
node.next = head.next;
head.next.pre = node;
head.next = node;
node.pre = head;
}
static class Node {
int key, val;
Node pre, next;
public Node(int key, int val) {
this.key = key;
this.val = val;
}
}
}