1. 顺序表及其应用 1.1 二进制求和 给你两个二进制字符串 a
和 b
,以二进制字符串的形式返回它们的和。
思路
进行二进制位运算,本位和s=a^b^c
,进位c=(a&b)|(a&c)|(b&c)
。
注意处理边界问题。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public String addBinary (String a, String b) { StringBuilder cnt=new StringBuilder (); int n=a.length()-1 ,m=b.length()-1 ; int c=0 ,s=0 ; while (n>=0 ||m>=0 ){ int n1=n>=0 ?a.charAt(n--)-'0' :0 ; int n2=m>=0 ?b.charAt(m--)-'0' :0 ; s=n1^n2^c; cnt.append(s); c=(n1&n2)|(n1&c)|(n2&c); } if (c!=0 ) cnt.append(c); return cnt.reverse().toString(); } }
1.2 移除元素 给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
思路
双指针法,左指针指向开头,右指针指向末尾,从头遍历,若元素值等于val
,则将其替换为右端第一个不为val
的元素。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int removeElement (int [] nums, int val) { int left=0 ,right=nums.length-1 ; while (left<=right){ if (nums[left]==val){ while (right>left&&nums[right]==val){ right--; } nums[left]=nums[right]; right--; } left++; } return right+1 ; } }
2. 有顺序表及其应用 2.1 删除有序数组中的重复项 给你一个 升序排列 的数组 nums
,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。
由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums
的第一部分。更规范地说,如果在删除重复项之后有 k
个元素,那么 nums
的前k
个元素应该保存最终结果。
将最终结果插入 nums
的前 k
个位置后返回k
。
不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
思路
双指针法,left
指向第一个不重复元素,依次遍历数组,将第一个不等于nums[left]
的元素赋给nums[left+1]
,left
指针加一,再重复上述操作。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int removeDuplicates (int [] nums) { int n=nums.length; int left=0 ; for (int i=1 ;i<n;i++){ while (i<n&&nums[left]==nums[i]){ i++; } if (i<n) nums[++left]=nums[i]; } return left+1 ; } }
2.2 删除有序数组中的重复项 II 给你一个有序数组 nums
,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
思路
双指针法,设置flag
标志位判断当前元素是否可以加入数组,后续解法同题解一。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public int removeDuplicates (int [] nums) { int n=nums.length; int slow=1 ,fast=1 ; boolean flag=true ; while (fast<n){ if (flag&&nums[fast]==nums[fast-1 ]){ nums[slow]=nums[fast]; flag=false ; slow++; } if (nums[fast]!=nums[fast-1 ]){ nums[slow]=nums[fast]; flag=true ; slow++; } fast++; } return slow; } }
附录
LeetCode 官方题解,思路连贯且清晰,且两道题关联紧密,代码模板相似。
删除有序数组中的重复项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int removeDuplicates (int [] nums) { int n = nums.length; if (n == 0 ) { return 0 ; } int fast = 1 , slow = 1 ; while (fast < n) { if (nums[fast] != nums[fast - 1 ]) { nums[slow] = nums[fast]; ++slow; } ++fast; } return slow; } }
删除有序数组中的重复项 II
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int removeDuplicates (int [] nums) { int n = nums.length; if (n <= 2 ) { return n; } int slow = 2 , fast = 2 ; while (fast < n) { if (nums[slow - 2 ] != nums[fast]) { nums[slow] = nums[fast]; ++slow; } ++fast; } return slow; } }
2.3 合并两个有序数组 给你两个按 非递减顺序 排列的整数数组 nums1
和 nums2
,另有两个整数 m
和 n
,分别表示 nums1
和 nums2
中的元素数目。
请你 合并 nums2
到 nums1
中,使合并后的数组同样按 非递减顺序 排列。
注意 :最终,合并后数组不应由函数返回,而是存储在数组 nums1
中。为了应对这种情况,nums1
的初始长度为 m + n
,其中前 m
个元素表示应合并的元素,后 n
个元素为 0
,应忽略。nums2
的长度为 n
。
方法一. 排序
思路
直观思路,将nums2
直接插入nums1
数组后排序。
这种方法时间复杂度较高,且未利用两者非递减顺序的特性。
代码
1 2 3 4 5 6 7 8 class Solution { public void merge (int [] nums1, int m, int [] nums2, int n) { for (int i=m;i<m+n;i++){ nums1[i]=nums2[i-m]; } Arrays.sort(nums1); } }
方法二. 双指针
思路
从数组末端开始,依次加入最大数,这样能保证nums1
中的数不被覆盖,相较同向双指针,逆向双指针的空间复杂度为 **O(1)**。
具体证明可参见LeetCode题解
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public void merge (int [] nums1, int m, int [] nums2, int n) { int p1=m-1 ,p2=n-1 ; int cur=m+n-1 ; int ans=0 ; while (p1>=0 ||p2>=0 ){ if (p1<0 ){ ans=nums2[p2--]; }else if (p2<0 ){ ans=nums1[p1--]; }else if (nums1[p1]>nums2[p2]){ ans=nums1[p1--]; }else { ans=nums2[p2--]; } nums1[cur--]=ans; } } }
2.4 寻找两个正序数组的中位数 给定两个大小分别为 m
和 n
的正序(从小到大)数组 nums1
和 nums2
。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
思路
合并数组后寻找中位数,这种方法的时间复杂度为**O(m+n)**,虽能通过,但不满足题目要求。
同向双指针合并数组,与题解三类似。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public double findMedianSortedArrays (int [] nums1, int [] nums2) { int n=nums1.length,m=nums2.length; int [] ans=new int [m+n]; int p1=0 ,p2=0 ; int cur=0 ; int val=0 ; while (p1<n||p2<m){ if (p1==n){ val=nums2[p2++]; }else if (p2==m){ val=nums1[p1++]; }else if (nums1[p1]<nums2[p2]){ val=nums1[p1++]; }else { val=nums2[p2++]; } ans[cur++]=val; } int tmp=(m+n)/2 ; if ((m+n)%2 ==0 ){ return (double )(ans[tmp]+ans[tmp-1 ])/2 ; } return ans[tmp]; } }
附录
LeetCode高分题解
link: 寻找两个正序数组的中位数
二分查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public double findMedianSortedArrays (int [] nums1, int [] nums2) { int n = nums1.length; int m = nums2.length; int left = (n + m + 1 ) / 2 ; int right = (n + m + 2 ) / 2 ; return (getKth(nums1, 0 , n - 1 , nums2, 0 , m - 1 , left) + getKth(nums1, 0 , n - 1 , nums2, 0 , m - 1 , right)) * 0.5 ; } private int getKth (int [] nums1, int start1, int end1, int [] nums2, int start2, int end2, int k) { int len1 = end1 - start1 + 1 ; int len2 = end2 - start2 + 1 ; if (len1 > len2) return getKth(nums2, start2, end2, nums1, start1, end1, k); if (len1 == 0 ) return nums2[start2 + k - 1 ]; if (k == 1 ) return Math.min(nums1[start1], nums2[start2]); int i = start1 + Math.min(len1, k / 2 ) - 1 ; int j = start2 + Math.min(len2, k / 2 ) - 1 ; if (nums1[i] > nums2[j]) { return getKth(nums1, start1, end1, nums2, j + 1 , end2, k - (j - start2 + 1 )); } else { return getKth(nums1, i + 1 , end1, nums2, start2, end2, k - (i - start1 + 1 )); } }
划分数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { public double findMedianSortedArrays (int [] A, int [] B) { int m = A.length; int n = B.length; if (m > n) { return findMedianSortedArrays(B,A); } int iMin = 0 , iMax = m; while (iMin <= iMax) { int i = (iMin + iMax) / 2 ; int j = (m + n + 1 ) / 2 - i; if (j != 0 && i != m && B[j-1 ] > A[i]){ iMin = i + 1 ; } else if (i != 0 && j != n && A[i-1 ] > B[j]) { iMax = i - 1 ; } else { int maxLeft = 0 ; if (i == 0 ) { maxLeft = B[j-1 ]; } else if (j == 0 ) { maxLeft = A[i-1 ]; } else { maxLeft = Math.max(A[i-1 ], B[j-1 ]); } if ( (m + n) % 2 == 1 ) { return maxLeft; } int minRight = 0 ; if (i == m) { minRight = B[j]; } else if (j == n) { minRight = A[i]; } else { minRight = Math.min(B[j], A[i]); } return (maxLeft + minRight) / 2.0 ; } } return 0.0 ; } }
3. 链表的实现 3.1 设计链表 你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:val
和 next
。val
是当前节点的值,next
是指向下一个节点的指针/引用。
如果是双向链表,则还需要属性 prev
以指示链表中的上一个节点。假设链表中的所有节点下标从 0
开始。
思路
设计单向链表或双向链表,模拟实现。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 public class ListNode { int val; ListNode next; ListNode(){} ListNode(int val){this .val=val;} ListNode(int val,ListNode next){this .val=val;this .next=next;} } class MyLinkedList { ListNode head; public MyLinkedList () { head=new ListNode (0 ); } public int get (int index) { ListNode p=head.next; while (p!=null &&index!=0 ){ p=p.next; index--; } if (p==null ) return -1 ; return p.val; } public void addAtHead (int val) { ListNode p=new ListNode (val,head.next); head.next=p; } public void addAtTail (int val) { ListNode p=head; while (p.next!=null ){ p=p.next; } p.next=new ListNode (val); } public void addAtIndex (int index, int val) { ListNode p=head; while (p!=null &&index!=0 ){ p=p.next; index--; } if (p==null ){ return ; } p.next=new ListNode (val,p.next); } public void deleteAtIndex (int index) { ListNode p=head; while (p!=null &&index!=0 ){ p=p.next; index--; } if (p!=null &&p.next!=null ){ p.next=p.next.next; } } }
3.2 链表随机节点 给你一个单链表,随机选择链表的一个节点,并返回相应的节点值。每个节点 被选中的概率一样 。
实现 Solution
类:
Solution(ListNode head)
使用整数数组初始化对象。
int getRandom()
从链表中随机选择一个节点并返回该节点的值。链表中所有节点被选中的概率相等。
思路
最初想法是开一个数组存储链表中所有数据,但是由于链表长度未知,空间复杂度较高。
采用水塘抽样算法。
知乎:随机算法:水塘抽样算法
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { ListNode root; Random random; public Solution (ListNode head) { root=head; random=new Random (); } public int getRandom () { int idx=1 ; int ans=0 ; for (ListNode p=root;p!=null ;p=p.next){ if (random.nextInt(idx++)==0 ){ ans=p.val; } } return ans; } }
4. 单链表及其应用 4.1 移除链表元素 给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
思路
模拟,依次判断当前节点的下一节点值是否等于目标值。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public ListNode removeElements (ListNode head, int val) { if (head==null ) return head; ListNode root=new ListNode (0 ,head); ListNode p=root; while (p!=null &&p.next!=null ){ if (p.next.val==val){ while (p.next!=null &&p.next.val==val){ p.next=p.next.next; } } p=p.next; } return root.next; } }
4.3 删除链表中的节点 有一个单链表的 head
,我们想删除它其中的一个节点 node
。
给你一个需要删除的节点 node
。你将 无法访问 第一个节点 head
。
链表的所有值都是 唯一的 ,并且保证给定的节点 node
不是链表中的最后一个节点。
删除给定的节点。注意,删除节点并不是指从内存中删除它。这里的意思是:
给定节点的值不应该存在于链表中。
链表中的节点数应该减少 1。
node
前面的所有值顺序相同。
node
后面的所有值顺序相同。
思路
由于不知道被删除节点的上一节点,因而无法在不影响后续节点的情况下将该节点删除。
将该节点下一节点的值赋给当前节点,再删除下一节点即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public void deleteNode (ListNode node) { node.val=node.next.val; node.next=node.next.next; } }
4.3 反转链表 给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
思路
递归求解,假设当前节点为p
,它后面的部分已全部被反转,现在要反转p
,即它要与它的直接后继节点调换位置,反转后p
为已经反转部分的尾节点,因此它的下一节点为null
。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public ListNode reverseList (ListNode head) { return reverse(head); } public ListNode reverse (ListNode head) { if (head==null ||head.next==null ){ return head; } ListNode root=reverse(head.next); head.next.next=head; head.next=null ; return root; } }
迭代解法
迭代方法需要使用两个临时节点,一个指向父节点,一个指向当前节点。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public ListNode reverseList (ListNode head) { ListNode pre=null ; ListNode cur=head; while (cur!=null ){ ListNode next=cur.next; cur.next=pre; pre=cur; cur=next; } return pre; } }
4.4 反转链表 II 给你单链表的头指针 head
和两个整数 left
和 right
,其中 left <= right
。请你反转从位置 left
到位置 right
的链表节点,返回 反转后的链表 。
思路
先遍历链表至left
处,随后对left
到right
区间进行反转,反转步骤类似题解三的迭代解法,在此过程中需记录left
的父节点与right
的子节点。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public ListNode reverseBetween (ListNode head, int left, int right) { ListNode root=new ListNode (0 ,head); ListNode cur=root; ListNode prev=cur; for (int i=0 ;i<left;i++){ prev=cur; cur=cur.next; } ListNode pre=null ; ListNode curr=cur; for (int i=left;i<=right;i++){ ListNode next=cur.next; cur.next=pre; pre=cur; cur=next; } prev.next=pre; curr.next=cur; return root.next; } }
4.5 奇偶链表 给定单链表的头节点 head
,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。
第一个 节点的索引被认为是 奇数 , 第二个 节点的索引为 偶数 ,以此类推。
请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。
你必须在 O(1)
的额外空间复杂度和 O(n)
的时间复杂度下解决这个问题。
思路
奇偶链表分开处理,最后再合并。
可以发现,奇节点的下一节点即为偶节点的下一节点,故此我们可以进行奇偶交替模拟,最后合并。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public ListNode oddEvenList (ListNode head) { if (head==null ) return head; ListNode odd=head; ListNode even=head.next; ListNode tmp=even; while (even!=null &&even.next!=null ){ odd.next=even.next; odd=odd.next; even.next=odd.next; even=even.next; } odd.next=tmp; return head; } }
4.6 分隔链表 给你一个链表的头节点 head
和一个特定值 x
,请你对链表进行分隔,使得所有 小于 x
的节点都出现在 大于或等于 x
的节点之前。
你应当 保留 两个分区中每个节点的初始相对位置。
思路
按照题意进行模拟,维护两个链表节点small
和large
,分别记录比x
小和大于等于x
的链表节点,最后将两个链表合并即可。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class Solution { public ListNode partition (ListNode head, int x) { if (head==null ){ return head; } ListNode small=new ListNode (0 ); ListNode large=new ListNode (0 ); ListNode p=small,q=large; while (head!=null ){ if (head.val<x){ p.next=head; p=p.next; }else { q.next=head; q=q.next; } head=head.next; } q.next=null ; p.next=large.next; return small.next; } }
4.7 两两交换链表中的节点 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
思路
递归求解,两两交换即当前节点的下一节点为新的头节点,而当前节点的下一节点为交换后链表的新节点,递归模拟。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public ListNode swapPairs (ListNode head) { if (head==null ||head.next==null ){ return head; } ListNode root=head.next; head.next=swapPairs(root.next); root.next=head; return root; } }
4.8 链表的中间结点 给你单链表的头结点 head
,请你找出并返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
思路
典型快慢指针题,使用slow
和fast
指针,初始时slow
和fast
都指向头结点,而后,慢指针每次前进一步,快指针每次前进两步,那么快指针到达链表末尾时,慢指针便指向了中间结点。
注意:链表结点数为偶数时,返回第二个中间结点,这会影响对快指针到达链表末尾的判定。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public ListNode middleNode (ListNode head) { if (head==null ||head.next==null ){ return head; } ListNode slow=head; ListNode fast=head; while (fast!=null &&fast.next!=null ){ slow=slow.next; fast=fast.next.next; } return slow; } }
4.9 回文链表 给你一个单链表的头节点 head
,请你判断该链表是否为回文链表。如果是,返回 true
;否则,返回 false
。
思路
直观思路:遍历链表,并将节点值存储在字符串或数组中,然后进行判断。
进阶:进阶要求O(n)
时间复杂度和O(1)
空间复杂度,可以先使用快慢指针找到中间节点,然后用迭代方法反转后半部分链表,最后进行比较。
(由于题目数据量不大,两种方法占用空间实际相差不大)
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public boolean isPalindrome (ListNode head) { if (head==null ||head.next==null ){ return true ; } StringBuilder cnt=new StringBuilder (); while (head!=null ){ cnt.append(head.val); head=head.next; } int n=cnt.length(); for (int i=0 ;i<n/2 ;i++){ if (cnt.charAt(i)!=cnt.charAt(n-i-1 )){ return false ; } } return true ; } }
4.10 重排链表 给定一个单链表 L
的头节点 head
,单链表 L
表示为:
1 L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:
1 L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
思路
遍历并存储链表节点,然后进行统一处理。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public void reorderList (ListNode head) { if (head==null ) return ; List<ListNode> cnt=new ArrayList <>(); while (head!=null ){ cnt.add(head); head=head.next; } int left=0 ,right=cnt.size()-1 ; while (left<right){ cnt.get(left).next=cnt.get(right); left++; if (left==right){ break ; } cnt.get(right).next=cnt.get(left); right--; } cnt.get(left).next=null ; } }
4.11 对链表进行插入排序 给定单个链表的头 head
,使用 插入排序 对链表进行排序,并返回 排序后链表的头 。
插入排序 算法的步骤:
插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
重复直到所有输入数据插入完为止。
思路
维护两个指针,一个指向当前待排序链表节点,一个指向已排序好后的尾节点。
若当前节点值小于当前尾节点,从已排序链表头节点开始,查找插入位置并将其插入。
若当前节点值大于等于当前尾节点,那么将当前节点插入末尾,再将当前尾节点后移一位即可。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class Solution { public ListNode insertionSortList (ListNode head) { if (head==null ) return head; ListNode root=new ListNode (0 ,head); ListNode tail=head,cur=head.next; while (cur!=null ){ if (tail.val<=cur.val){ tail=cur; }else { ListNode prev=root; while (prev.next.val<=cur.val){ prev=prev.next; } tail.next=cur.next; cur.next=prev.next; prev.next=cur; } cur=tail.next; } return root.next; } }
4.12 K 个一组翻转链表 给你链表的头节点 head
,每 k
个节点一组进行翻转,请你返回修改后的链表。
k
是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k
的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
思路
模拟翻转,首先寻找到第 k 个节点,然后将该部分翻转,以此类推。
翻转链表可参考前面题解“反转链表” 。
注意:翻转前需要存储前一节点与后一节点,以便将翻转后的链表接回原链表。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class Solution { public ListNode reverseKGroup (ListNode head, int k) { ListNode root=new ListNode (0 ,head); ListNode pre=root; while (head!=null ){ ListNode tail=pre; for (int i=0 ;i<k;i++){ tail=tail.next; if (tail==null ){ return root.next; } } ListNode[] tmp=reverse(head,tail); head=tmp[0 ]; tail=tmp[1 ]; pre.next=head; pre=tail; head=tail.next; } return root.next; } public ListNode[] reverse(ListNode head,ListNode tail){ ListNode pre=tail.next; ListNode cur=head; while (pre!=tail){ ListNode next=cur.next; cur.next=pre; pre=cur; cur=next; } return new ListNode []{tail,head}; } }
4.13 分隔链表 给你一个头结点为 head
的单链表和一个整数 k
,请你设计一个算法将链表分隔为 k
个连续的部分。
每部分的长度应该尽可能的相等:任意两部分的长度差距不能超过 1 。这可能会导致有些部分为 null 。
这 k
个部分应该按照在链表中出现的顺序排列,并且排在前面的部分的长度应该大于或等于排在后面的长度。
返回一个由上述 k
部分组成的数组。
思路
按照题意进行模拟即可。
注意:若节点总数不是k的倍数,那么就在前面几个部分多加一个节点。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class Solution { public ListNode[] splitListToParts(ListNode head, int k) { int len=0 ; ListNode p=head; while (p!=null ){ len++; p=p.next; } int size=len/k,carry=len%k; ListNode[] ans=new ListNode [k]; ListNode cur=head; for (int i=0 ;i<k&&cur!=null ;i++){ ans[i]=cur; int cur_s=size+(carry-->0 ?1 :0 ); for (int j=1 ;j<cur_s;j++){ cur=cur.next; } ListNode next=cur.next; cur.next=null ; cur=next; } return ans; } }
5. 有序链表及其应用 5.1 删除排序链表中的重复元素 给定一个已排序的链表的头 head
, 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
思路
维护两个指针slow
和fast
,慢指针指向无重复的链表,快指针指向原链表,依次比较slow
和fast
的值,决定其是否加入慢指针指向的链表中。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public ListNode deleteDuplicates (ListNode head) { if (head==null ) return head; ListNode slow=head; ListNode fast=head.next; while (fast!=null ){ if (fast.val!=slow.val){ slow.next=fast; slow=slow.next; } fast=fast.next; } slow.next=null ; return head; } }
5.2 删除排序链表中的重复元素 II 给定一个已排序的链表的头 head
, 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。
思路
该题是上一题的进阶,首先定义一个哑节点root
,便于删除头节点,然后依次比较下一节点与下下一节点的值,若相等,则继续比较寻找第一个不等的节点,并将下一节点指向它;若不等,那么直接将当前节点后移即可。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public ListNode deleteDuplicates (ListNode head) { ListNode root=new ListNode (0 ,head); ListNode p=root; while (p.next!=null &&p.next.next!=null ){ if (p.next.val==p.next.next.val){ int val=p.next.val; while (p.next!=null &&p.next.val==val){ p.next=p.next.next; } }else { p=p.next; } } return root.next; } }
5.3 合并两个有序链表 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
思路
由于两个链表均为升序排列,因此通过比较节点值大小按照题意进行模拟即可。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class Solution { public ListNode mergeTwoLists (ListNode list1, ListNode list2) { if (list1==null ){ return list2; } if (list2==null ){ return list1; } ListNode root=new ListNode (0 ); ListNode p=root; while (list1!=null ||list2!=null ){ if (list1==null ){ p.next=list2; break ; }else if (list2==null ){ p.next=list1; break ; }else { if (list1.val<=list2.val){ p.next=list1; list1=list1.next; }else { p.next=list2; list2=list2.next; } } p=p.next; } return root.next; } }
5.4 合并K个升序链表 给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
思路
直观思路:参考“合并两个有序链表 ”,每次将合并后的链表与未合并的链表进行合并即可。
进阶:使用分治或优先队列方法解决。
顺序合并
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 class Solution { public ListNode mergeKLists (ListNode[] lists) { int n=lists.length; if (n==0 ){ return null ; } ListNode p=lists[0 ]; for (int i=0 ;i<n-1 ;i++){ p=merge(p,lists[i+1 ]); } return p; } public ListNode merge (ListNode list1,ListNode list2) { if (list1==null ){ return list2; } if (list2==null ){ return list1; } ListNode root=new ListNode (0 ); ListNode p=root; while (list1!=null ||list2!=null ){ if (list1==null ){ p.next=list2; break ; }else if (list2==null ){ p.next=list1; break ; }else { if (list1.val<list2.val){ p.next=list1; list1=list1.next; }else { p.next=list2; list2=list2.next; } } p=p.next; } return root.next; } }
分治合并
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 class Solution { public ListNode mergeKLists (ListNode[] lists) { return mergeList(lists,0 ,lists.length-1 ); } public ListNode mergeList (ListNode[] lists,int left,int right) { if (left==right){ return lists[left]; } if (left>right){ return null ; } int mid=(left+right)/2 ; return merge(mergeList(lists,left,mid),mergeList(lists,mid+1 ,right)); } public ListNode merge (ListNode list1,ListNode list2) { if (list1==null ){ return list2; } if (list2==null ){ return list1; } ListNode root=new ListNode (0 ); ListNode p=root; while (list1!=null ||list2!=null ){ if (list1==null ){ p.next=list2; break ; }else if (list2==null ){ p.next=list1; break ; }else { if (list1.val<list2.val){ p.next=list1; list1=list1.next; }else { p.next=list2; list2=list2.next; } } p=p.next; } return root.next; } }