链表:
1.reverse-nodes-in-k-group(k组翻转链表)【hard】
给你一个链表以及一个k,将这个链表从头指针开始每k个翻转一下。链表元素个数不是k的倍数,最后剩余的不用翻转。

/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ public class Solution { /** * @param head a ListNode * @param k an integer * @return a ListNode */ public ListNode reverseKGroup(ListNode head, int k) { // Write your code here if (head == null || k <= 1) { return head; } ListNode dummy = new ListNode(0); dummy.next = head; head = dummy; while (head.next != null) { head = reverseNextK(head, k); } return dummy.next; } private ListNode reverseNextK(ListNode head, int k) { ListNode node = head; for (int i = 0; i < k; i++) { if (node.next == null) { return head.next; } node = node.next; } ListNode n1 = head.next; ListNode prev = head; ListNode curt = head.next; for (int i = 0; i < k; i++) { ListNode temp = curt.next; curt.next = prev; prev = curt; curt = temp; } n1.next = curt; head.next = prev; return n1; } }
注意:n0->n1->n2->...->nk->nk+1若要翻转n1->...->nk,则n0到nk节点都会变化。 手动创建dummy node(哨兵节点),dummy.next总是head(头结点),最后返回dummy.next也即head节点。reverseNextK()函数分三步:1.检查是否有足够的k个节点可翻转(如果没有,返回head.next,因为此时的head节点实际已在上次操作中被翻转)2.翻转( n1 = head.next; prev = head; curt = head.next;for (int i = 0; i < k; i++) { temp = curt.next; curt.next = prev; prev = curt; curt = temp;})3.链接,以便继续下次翻转(n1.next = curt; head.next = prev; return n1;)
2.reverse-linked-list(翻转链表)
翻转一个链表。给出一个链表1->2->3->null,这个翻转后的链表为3->2->1->null。

/** * Definition for ListNode. * public class ListNode { * int val; * ListNode next; * ListNode(int val) { * this.val = val; * this.next = null; * } * } */ public class Solution { /** * @param head: The head of linked list. * @return: The new head of reversed linked list. */ public ListNode reverse(ListNode head) { // write your code here ListNode prev = null; ListNode curt = head; while (curt != null) { ListNode temp = curt.next; curt.next = prev; prev = curt; curt = temp; } return prev; } }
3.reverse-linked-list-ii(翻转链表II)
翻转链表中第m个节点到第n个节点的部分。m,n满足1 ≤ m ≤ n ≤ 链表长度。

/** * Definition for ListNode * public class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { /** * @param ListNode head is the head of the linked list * @oaram m and n * @return: The head of the reversed ListNode */ public ListNode reverseBetween(ListNode head, int m , int n) { // write your code if (head == null || m >= n) { return head; } ListNode dummy = new ListNode(0); dummy.next = head; head = dummy; for (int i = 1; i < m; i++) { //if (head == null) { //return null; //} head = head.next; } ListNode premNode = head; ListNode mNode = head.next; ListNode nNode = mNode; ListNode postnNode = mNode.next; for (int i = m; i < n; i++) { //if (postnNode == null) { //return null; //} ListNode temp = postnNode.next; postnNode.next = nNode; nNode = postnNode; postnNode = temp; } mNode.next = postnNode; premNode.next = nNode; return dummy.next; } }
注意:1. 创建dummy节点记录head。2. 找到并记录m的前一个节点premNode。3. 记录premNode的下一个节点mNode,它将会是reversed linked list的尾部。4.使用nNode(初始化为mNode)和postnNode(初始化为mNode.next)翻转指定区间的链表,翻到最后一个节点(nNode)时,把mNode.next指向它的next(postnNode)。把premNode.next指向nNode,这样就把翻转链表与之前、之后的链表链接。5. 返回dummynode.next。【注释部分不太理解返回值的意义。】
4.partition-list(链表划分)
给定一个单链表和数值x,划分链表使得所有小于x的节点排在大于等于x的节点之前。你应该保留两部分内链表节点原有的相对顺序。

/** * Definition for ListNode. * public class ListNode { * int val; * ListNode next; * ListNode(int val) { * this.val = val; * this.next = null; * } * } */ public class Solution { /** * @param head: The first node of linked list. * @param x: an integer * @return: a ListNode */ public ListNode partition(ListNode head, int x) { // write your code here if (head == null) { return head; } ListNode leftDummy = new ListNode(0); ListNode rightDummy = new ListNode(0); ListNode left = leftDummy; ListNode right = rightDummy; while (head != null) { if (head.val < x) { left.next = head; left = head; } else { right.next = head; right = head; } head = head.next; } left.next = rightDummy.next; right.next = null; return leftDummy.next; } }
注意:定义leftDummy和rightDummy两个哨兵节点,分别用来保存<x和≥x节点,最后进行链接即可。在left(right).next=head之后,left(right)也要=head。
5.merge-two-sorted-lists(合并两个排序链表)
将两个排序链表合并为一个新的排序链表。

/** * Definition for ListNode. * public class ListNode { * int val; * ListNode next; * ListNode(int val) { * this.val = val; * this.next = null; * } * } */ public class Solution { /** * @param ListNode l1 is the head of the linked list * @param ListNode l2 is the head of the linked list * @return: ListNode head of linked list */ public ListNode mergeTwoLists(ListNode l1, ListNode l2) { // write your code here ListNode dummy = new ListNode(0); ListNode head = dummy; while (l1 != null && l2 != null) { if (l1.val < l2.val) { head.next = l1; l1 = l1.next; } else { head.next = l2; l2 = l2.next; } head = head.next; } if (l1 != null) { head.next = l1; } else { head.next = l2; } return dummy.next; } }
注意:经典合并法。当两个链表都不为空时,比较节点值的大小...当其中一个链表不为空时...
6.swap-two-nodes-in-linked-list(交换链表中的两个节点)
给你一个链表以及两个权值v1
和v2
,交换链表中权值为v1
和v2
的这两个节点。 保证链表中节点权值各不相同,如果没有找到对应节点,那么什么也不用做。你需要交换两个节点而不是改变节点的权值。

/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ public class Solution { /** * @param head a ListNode * @oaram v1 an integer * @param v2 an integer * @return a new head of singly-linked list */ public ListNode swapNodes(ListNode head, int v1, int v2) { // Write your code here ListNode dummy = new ListNode(0); dummy.next = head; ListNode cur = dummy; ListNode node1Prev = null; ListNode node2Prev = null; while (cur.next != null) { if (cur.next.val == v1) { node1Prev = cur; } else if (cur.next.val == v2) { node2Prev = cur; } cur = cur.next; } if (node1Prev == null || node2Prev == null) { return head; } if (node2Prev.next == node1Prev) { // make sure node1Prev is before node2Prev ListNode t = node1Prev; node1Prev = node2Prev; node2Prev = t; } ListNode node1 = node1Prev.next; ListNode node2 = node2Prev.next; ListNode node2Next = node2.next; if (node1Prev.next == node2Prev) { node1Prev.next = node2; node2.next = node1; node1.next = node2Next; } else { node1Prev.next = node2; node2.next = node1.next; node2Prev.next = node1; node1.next = node2Next; } return dummy.next; } }
注意:1.找到权值为v1
和v2
的两个节点的前一个节点(node1Prev,node2Prev,如果二者有一个为空,直接返回即可)。2.保证node1Prev在node2Prev前面,排除node2在前,node1在后且相邻的情况。3.交换两个节点,首先要记录node2.next(node2Next),因为后续操作该值会发生变化。分两种情况进行交换:(1) node1在前,node2在后且相邻;(node1Prev.next = node2;node2.next = node1;node1.next = node2Next;)(2)node1在前,node2在后不相邻。(node1Prev.next = node2;node2.next = node1.next;node2Prev.next = node1;node1.next = node2Next;)
7.reorder-list(链表重排序)
给定单链表L:L0→L1→...→Ln-1→Ln,重新排序为:L0→Ln→L1→Ln-1→L2→Ln-2→...

/** * Definition for ListNode. * public class ListNode { * int val; * ListNode next; * ListNode(int val) { * this.val = val; * this.next = null; * } * } */ public class Solution { /** * @param head: The head of linked list. * @return: void */ private ListNode reverse(ListNode head) { ListNode prev = null; ListNode curt = head; while (curt != null) { ListNode temp = curt.next; curt.next = prev; prev = curt; curt = curt.next; } return prev; } private void merge(ListNode head1, ListNode head2) { int index = 0; ListNode dummy = new ListNode(0); while (head1 != null && head2 != null) { if (index % 2 == 0) { dummy.next = head1; head1 = head1.next; } else { dummy.next = head2; head2 = head2.next; } dummy = dummy.next; index ++; } if (head1 != null) { dummy.next = head1; } else { dummy.next = head2; } } private ListNode findMiddle(ListNode head) { ListNode slow = head; ListNode fast = head.next; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } return slow; } public void reorderList(ListNode head) { // write your code here if (head == null || head.next == null) { return head; } ListNode mid = findMiddle(head); ListNode tail = reverse(mid.next); mid.next = null; return merger(head, tail); } }
注意:1.使用快慢指针法找到链表的中点:findMiddle()函数。2.翻转中点右侧的链表:reverse()函数。3.逐个合并左右两侧的链表中的节点:merge()函数。使用index变量根据index值的奇偶性判断链接哪边的节点。
8.rotate-list(旋转链表)
给定列表,将列表向右旋转k个位置,其中k是非负数。

/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { /** * @param head: the List * @param k: rotate to the right k places * @return: the list after rotation */ private int getLength(ListNode head) { int length = 0; while (head != null) { length++; head = head.next; } return length; } public ListNode rotateRight(ListNode head, int k) { // write your code here if (head == null) { return head; } int length = getLength(head); k = k % length; ListNode dummy = new ListNode(0); dummy.next = head; head = dummy; ListNode tail = dummy; for (int i = 0; i < k; i++) { head = head.next; } while (head.next != null) { head = head.next; tail = tail.next; } head.next = dummy.next; dummy.next = tail.next; tail.next = null; return dummy.next; } }
注意:k可能大于链表的长度length,所以要先求出链表的长度然后取模。以链表1->2->3->4->5->null为例,head节点先向前移动k个位置(head=2),然后head节点和tail节点同时向前移动,直至head节点到达链表末尾(head:3,4,5 tail:1,2,3)。最后head.next = dummy.next(5->1),dummy.next = tail.next(4),tail.next=null。
9.copy-list-with-random-pointer(复制带随机指针的链表)
给出一个链表,每个节点包含一个额外增加的随机指针可以指向链表中的任何节点或空的节点。返回一个深拷贝的链表。
不使用HashMap的解法:【要记住】

/** * Definition for singly-linked list with a random pointer. * class RandomListNode { * int label; * RandomListNode next, random; * RandomListNode(int x) { this.label = x; } * }; */ public class Solution { /** * @param head: The head of linked list with a random pointer. * @return: A new head of a deep copy of the list. */ private void copyNext(RandomListNode head) { while (head != null) { RandomListNode newNode = new RandomListNode(head.label); newNode.next = head.next; newNode.random = head.random; head.next = newNode; head = head.next.next; } } private void copyRandom(RandomListNode head) { while (head != null) { if (head.next.random != null) { head.next.random = head.random.next; } head = head.next.next; } } private RandomListNode splitList(RandomListNode head) { RandomListNode newHead = head.next; while (head != null) { RandomListNode temp = head.next; head.next = temp.next; head = head.next; if (temp.next != null) { temp.next = temp.next.next; } } return newHead; } public RandomListNode copyRandomList(RandomListNode head) { // write your code here if (head == null) { return head; } copyNext(head); copyRandom(head); return splitList(head); } }
注意:第一遍扫的时候巧妙运用next指针,开始数组是1->2->3->4。然后扫描过程中 先建立copy节点 1->1`->2->2`->3->3`->4->4`(copyNext()函数), 然后第二遍copy的时候去建立边的copy(copyRandom()函数),拆分节点, 一边扫描一边拆成两个链表(splitList()函数),这里用到两个dummy node。第一个链表变回1->2->3->4, 然后第二变成 1`->2`->3`->4`。
使用HashMap的解法:

/** * Definition for singly-linked list with a random pointer. * class RandomListNode { * int label; * RandomListNode next, random; * RandomListNode(int x) { this.label = x; } * }; */ public class Solution { /** * @param head: The head of linked list with a random pointer. * @return: A new head of a deep copy of the list. */ public RandomListNode copyRandomList(RandomListNode head) { // write your code here if (head == null) { return null; } HashMap<RandomListNode, RandomListNode> map = new HashMap<RandomListNode, RandomListNode>(); RandomListNode dummy = new RandomListNode(0); RandomListNode pre = dummy; RandomListNode newNode; while (head != null) { if (map.containsKey(head)) { newNode = map.get(head); } else { newNode = new RandomListNode(head.label); map.put(head, newNode); } pre.next = newNode; if (head.random != null) { if (map.containsKey(head.random)) { newNode.random = map.get(head.random); } else { newNode.random = new RandomListNode(head.random.label); map.put(head.random, newNode.random); } } pre = newNode; head = head.next; } return dummy.next; } }
10.linked-list-cycle(带环链表)
给定一个链表,判断它是否有环。

public class Solution { /** * @param head: The first node of linked list. * @return: True if it has a cycle, or false */ public boolean hasCycle(ListNode head) { // write your code here if (head == null || head.next == null) { return false; } ListNode slow = head; ListNode fast = head.next; while (slow != fast) { if (fast == null || fast.next == null) { return false; } fast = fast.next.next; slow = slow.next; } return true; } }
注意:使用快慢指针法,如果快慢指针能够重合证明有环。
11.linked-list-cycle-ii(带环链表II)【hard】
给定一个链表,如果链表中存在环,则返回到链表中环的起始节点的值,如果没有环,返回null。

/** * Definition for ListNode. * public class ListNode { * int val; * ListNode next; * ListNode(int val) { * this.val = val; * this.next = null; * } * } */ public class Solution { /** * @param head: The first node of linked list. * @return: The node where the cycle begins. * if there is no cycle, return null */ public ListNode detectCycle(ListNode head) { // write your code here if (head == null || head.next == null) { return null; } ListNode slow = head; ListNode fast = head.next; while (slow != fast) { if (fast == null || fast.next == null) { return null; } fast = fast.next.next; slow = slow.next; } slow = head; fast = fast.next; while (slow != fast) { slow = slow.next; fast = fast.next; } return slow; } }
注意:1.使用快慢指针法找到fast=slow的节点。2.slow=head,fast=fast.next,两个指针逐个后移,找到slow=fast的节点即交叉点。
12.intersection-of-two-linked-lists(两个链表的交叉)【要记住】
请写一个程序,找到两个单链表最开始的交叉节点。如果两个链表没有交叉,返回null
。在返回结果后,两个链表仍须保持原有的结构。可假定整个链表结构中没有循环。

/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { /** * @param headA: the first list * @param headB: the second list * @return: a ListNode */ public ListNode getIntersectionNode(ListNode headA, ListNode headB) { // Write your code here if (headA == null || headB == null) { return null; } //step one ListNode node = headA; while (node.next != null) { node = node.next; } node.next = headB; //step two、three ListNode result = helper(headA); //step four node.next = null; return result; } private ListNode helper(ListNode head) { //step two ListNode slow = head; ListNode fast = head.next; while (slow != fast) { if (fast == null || fast.next == null) { return null; } slow = slow.next; fast = fast.next.next; } //step three slow = head; fast = fast.next; while (slow != fast) { slow = slow.next; fast = fast.next; } return slow; } }
注意:1.找到A链表的尾部链接B链表。2.使用快慢指针法找到fast=slow的节点。3.slow=head,fast=fast.next,两个指针逐个后移,找到slow=fast的节点即交叉点。4.将A链表的尾部置null,恢复原状。
13.sort-list(链表排序)
在 O(n log n) 时间复杂度和常数级的空间复杂度下给链表排序。
归并排序解法:

/** * Definition for ListNode. * public class ListNode { * int val; * ListNode next; * ListNode(int val) { * this.val = val; * this.next = null; * } * } */ public class Solution { /** * @param head: The head of linked list. * @return: You should return the head of the sorted linked list, using constant space complexity. */ private ListNode findMiddle(ListNode head) { ListNode slow = head; ListNode fast = head.next; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } return slow; } private ListNode merge(ListNode head1, ListNode head2) { ListNode dummy = new ListNode(0); ListNode head = dummy; while (head1 != null && head2 != null) { if (head1.val < head2.val) { head.next = head1; head1 = head1.next; } else { head.next = head2; head2 = head2.next; } head = head.next; } if (head1 != null) { head.next = head1; } else { head.next = head2; } return dummy.next; } public ListNode sortList(ListNode head) { // write your code here if (head == null || head.next == null) { return head; } ListNode mid = findMiddle(head); ListNode right = sortList(mid.next); mid.next = null; ListNode left = sortList(head); return merge(left, right); } }
注意:使用快慢指针法找到中点findMiddle(),左右各sortList(),再merge()。
快速排序解法:

/** * Definition for ListNode. * public class ListNode { * int val; * ListNode next; * ListNode(int val) { * this.val = val; * this.next = null; * } * } */ public class Solution { /** * @param head: The head of linked list. * @return: You should return the head of the sorted linked list, using constant space complexity. */ private ListNode findMiddle(ListNode head) { ListNode slow = head; ListNode fast = head.next; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } return slow; } private ListNode concat(ListNode left, ListNode middle, ListNode right) { ListNode dummy = new ListNode(0); ListNode tail = dummy; tail.next = left; tail = getTail(tail); tail.next = middle; tail = getTail(tail); tail.next = right; tail = getTail(tail); return dummy.next; } private ListNode getTail(ListNode head) { if (head == null) { return null; } while (head.next != null) { head = head.next; } return head; } public ListNode sortList(ListNode head) { // write your code here if (head == null || head.next == null) { return head; } ListNode mid = findMiddle(head); ListNode leftDummy = new ListNode(0); ListNode leftTail = leftDummy; ListNode rightDummy = new ListNode(0); ListNode rightTail = rightDummy; ListNode middleDummy = new ListNode(0); ListNode middleTail = middleDummy; while (head != null) { if (head.val < mid.val) { leftTail.next = head; leftTail = head; } else if (head.val > mid.val) { rightTail.next = head; rightTail = head; } else { middleTail.next = head; middleTail = head; } head = head.next; } leftTail.next = null; middleTail.next = null; rightTail.next = null; ListNode left = sortList(leftDummy.next); ListNode right = sortList(rightDummy.next); return concat(left, middleDummy.next, right); } }
注意:使用快慢指针法找到中点findMiddle(),按小于,等于,大于排成三个链表leftDummy, middleDummy,rightDummy,然后concat()(在链接的过程中要找到已合并链表的尾部getTail()函数)。
在定义dummy节点的同时,一般会定义一个新节点tail(或head)=dummy,在后续操作过程中改变tail.next,返回该链表头或者执行其它函数时使用dummy.next。
10题和11题的快慢指针法如下:

ListNode slow = head; ListNode fast = head.next; while (slow != fast) { if (fast == null || fast.next == null) { return null; } slow = slow.next; fast = fast.next.next; }
7题和12题的findMiddle()函数中快慢指针法如下:

private ListNode findMiddle(ListNode head) { ListNode slow = head; ListNode fast = head.next; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } return slow; }
14.delete-node-in-the-middle-of-singly-linked-list(在O(1)时间复杂度删除链表节点)
给定一个单链表中的一个等待被删除的节点(非表头或表尾)。请在在O(1)时间复杂度删除该链表节点。

/** * Definition for ListNode. * public class ListNode { * int val; * ListNode next; * ListNode(int val) { * this.val = val; * this.next = null; * } * } */ public class Solution { /** * @param node: the node in the list should be deleted * @return: nothing */ public void deleteNode(ListNode node) { // write your code here if (node == null || node.next == null) { return; } node.val = node.next.val; node.next = node.next.next; } }
注意:node.val = node.next.val; node.next = node.next.next;(node为待删除节点)
15.convert-sorted-list-to-balanced-bst(排序列表转换为二叉查找树)
给出一个所有元素以升序排序的单链表,将它转换成一棵高度平衡的二分查找树。

/** * Definition for ListNode. * public class ListNode { * int val; * ListNode next; * ListNode(int val) { * this.val = val; * this.next = null; * } * } * Definition of TreeNode: * public class TreeNode { * public int val; * public TreeNode left, right; * public TreeNode(int val) { * this.val = val; * this.left = this.right = null; * } * } */ public class Solution { /** * @param head: The first node of linked list. * @return: a tree node */ private ListNode current; private int getListLength(ListNode head) { int length = 0; while (head != null) { length++; head = head.next; } return length; } public TreeNode sortedListToBST(ListNode head) { // write your code here current = head; int size = getListLength(head); return sortedListToBSTHelper(size); } private TreeNode sortedListToBSTHelper(int size) { if (size <= 0) { return null; } TreeNode left = sortedListToBSTHelper(size / 2); TreeNode root = new TreeNode(current.val); current = current.next; TreeNode right = sortedListToBSTHelper(size - 1 - size / 2); root.left = left; root.right = right; return root; } }
注意:按照“左->根->右”中序遍历的顺序来转换。首先要得到链表的长度(size),然后执行递归操作:

TreeNode left = sortedListToBSTHelper(size / 2);//左 TreeNode root = new TreeNode(current.val);//根 current = current.next; TreeNode right = sortedListToBSTHelper(size - 1 - size / 2);//右 root.left = left; root.right = right;
求链表长度的函数:

private int getListLength(ListNode head) { int length = 0; while (head != null) { length++; head = head.next; } return length; }
16.insert-into-a-cyclic-sorted-list(插入循环排序链表)
给定已经排序的循环链表中的一个节点,写一个函数以将值value插入链表,使其保持循环排序链表。给定节点可以是链表中的任何单个节点。返回插入的新节点。3-> 5-> 1是循环链表,所以3是1的下一个节点。3-> 5-> 1与5-> 1-> 3相同。
例:给定一个链表3->5->1,若插入一个值4:返回5-> 1-> 3-> 4 这种情况下x(4)>=prev.val(3) && x(4)<=curt.val(5)
若插入一个值6:返回5-> 6-> 1-> 3 这种情况下prev.val(5)>curt.val(1) && (x(6)>prev.val(5) || x(6)<curt.val(1))

/** * Definition for ListNode * public class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { /** * @param node a list node in the list * @param x an integer * @return the inserted new list node */ public ListNode insert(ListNode node, int x) { // Write your code here if (node == null) { node = new ListNode(x); node.next = node; return node; } ListNode curt = node; ListNode prev = null; do { prev = curt; curt = curt.next; if (x >= prev.val && x <= curt.val) { break; } if (prev.val > curt.val && (x > prev.val || x < curt.val)) { break; } } while (curt != node); ListNode newNode = new ListNode(x); newNode.next = curt; prev.next = newNode; return newNode; } }
注意:首先,如果给定链表为空,则新建只有一个节点的循环链表。如果给定链表不为空,依照例子中两种情况进行操作。
数组:
1.子数组 Subarray
令 PrefixSum[i] = A[0] + A[1] + … +A[i - 1], PrefixSum[0] = 0。易知构造PrefixSum耗费 O(n) 时间和 O(n) 空间。如需计算子数组从下标i到下标j之间的所有数之和,
则有 Sum(i~j) = PrefixSum[j + 1] - PrefixSum[i]。
17.maximum-subarray(最大子数组)
给定一个整数数组,找到一个具有最大和的子数组,返回其最大和。子数组最少包含一个数。
贪心解法:

public class Solution { /* * @param : A list of integers * @return: A integer indicate the sum of max subarray */ public int maxSubArray(int[] nums) { // write your code here if (nums == null || nums.length == 0) { return 0; } int max = Integer.MIN_VALUE; int sum = 0; for (int i = 0; i < nums.length; i++) { sum += nums[i]; max = Math.max(max, sum); sum = Math.max(sum, 0); } return max; } };
注意:使用max变量记录子数组的最大和,sum变量记录数组和。
在for循环的过程中,sum值逐渐累加,然后max取当前max和sum的较大值,sum取当前sum和0的较大值。最后返回max值。
使用PrefixSum数组的解法:

public class Solution { /* * @param : A list of integers * @return: A integer indicate the sum of max subarray */ public int maxSubArray(int[] nums) { // write your code here if (nums == null || nums.length == 0) { return 0; } int max = Integer.MIN_VALUE; int sum = 0; int minSum = 0; for (int i = 0; i < nums.length; i++) { sum += nums[i]; max = Math.max(max, sum - minSum); minSum = Math.min(minSum, sum); } return max; } };
注意:使用max变量记录子数组的最大和,sum变量记录数组和,minSum变量记录当前prefixSum的最小值(prefixSum应尽可能小)。
在for循环的过程中,sum值逐渐累加,然后max取当前max和sum-minSum的较大值,minSum取当前minSum和sum的较小值。最后返回max值。
18.maximum-subarray-ii(最大子数组II)
给定一个整数数组,找出两个不重叠子数组使得它们的和最大。每个子数组的数字在数组中的位置应该是连续的。返回最大的和。子数组最少包含一个数。

public class Solution { /** * @param nums: A list of integers * @return: An integer denotes the sum of max two non-overlapping subarrays */ public int maxTwoSubArrays(ArrayList<Integer> nums) { // write your code int size = nums.size(); int[] left = new int[size]; int[] right = new int[size]; //left maximum subarray int sum = 0; int minSum = 0; int max = Integer.MIN_VALUE; for (int i = 0; i < size; i++) { sum += nums.get(i); max = Math.max(max, sum - minSum); minSum = Math.min(minSum, sum); left[i] = max; } //right maximum subarray sum = 0; minSum = 0; max = Integer.MIN_VALUE; for (int i = size - 1; i >= 0; i--) { sum += nums.get(i); max = Math.max(max, sum - minSum); minSum = Math.min(minSum, sum); right[i] = max; } //max left + right max = Integer.MIN_VALUE; for (int i = 0; i < size - 1; i++) { max = Math.max(max, left[i] + right[i + 1]); } return max; } }
注意:这个题的思路是,因为两个subarray 一定不重叠,所以必定存在一条分割线分开这两个 subarrays,所以 最后的部分里:
max = Integer.MIN_VALUE;
for(int i = 0; i < size - 1; i++){
max = Math.max(max, left[i] + right[i + 1]);
}
return max;
这里是在枚举 这条分割线的位置,然后 left[] 和 right[] 里分别存的是,某个位置往左的 maximum subarray 和往右的 maximum subarray。
左右部分的最大子数组和由17题中的方法求得,注意右半部分要倒序进行。
19.maximum-subarray-iii(最大子数组III)【hard】
给定一个整数数组和一个整数 k,找出 k 个不重叠子数组使得它们的和最大。每个子数组的数字在数组中的位置应该是连续的。返回最大的和。子数组最少包含一个数。
方法一:划分类DP

public class Solution { /** * @param nums: A list of integers * @param k: An integer denote to find k non-overlapping subarrays * @return: An integer denote the sum of max k non-overlapping subarrays */ public int maxSubArray(int[] nums, int k) { // write your code here if (nums.length < k) { return 0; } int len = nums.length; int[][] globalMax = new int[k + 1][len + 1]; int[][] localMax = new int[k + 1][len + 1]; for (int i = 1; i <= k; i++) { localMax[i][i - 1] = Integer.MIN_VALUE; //小于 i 的数组不能够partition for (int j = i; j <= len; j++) { localMax[i][j] = Math.max(localMax[i][j - 1], globalMax[i - 1][j - 1]) + nums[j - 1]; if (j == i) { globalMax[i][j] = localMax[i][j]; } else { globalMax[i][j] = Math.max(globalMax[i][j - 1], localMax[i][j]); } } } return globalMax[k][len]; } }
方法二:

public class Solution { /** * @param nums: A list of integers * @param k: An integer denote to find k non-overlapping subarrays * @return: An integer denote the sum of max k non-overlapping subarrays */ public int maxSubArray(int[] nums, int k) { // write your code here int len = nums.size(); int[][] f = new int[k + 1][len]; for (int i = 1; i < k + 1; i++) { int sum = 0; for (int j = 0; j < i; j++) { sum += nums.get(j); } f[i][i - 1] = sum; } for (int i = 1; i < len; i++) { f[1][i] = Math.max(f[1][i - 1] + nums.get(i), nums.get(i)); } for (int i = 2; i < k + 1; i++) { for (int n = i; n < len; n++) { int curMax = f[i][n - 1] + nums.get(n); for (int j = i - 2; j < n; j++) { if ((f[i - 1][j] + nums.get(n)) > curMax) { curMax = f[i - 1][j] + nums.get(n); } } f[i][n] = curMax; } } int res = Integer.MIN_VALUE; for (int i = k - 1; i < len; i++){ if (f[k][i] > res) { res = f[k][i]; } } return res; } }
20.maximum-subarray-iv(最大子数组IV)
给定一个整数数组,找到一个连续的子阵列,其具有最大和,且长度应该大于或等于给定长度k。返回最大的和,如果数组中的元素少于k个,则返回0。

public class Solution { /** * @param nums an array of integers * @param k an integer * @return the largest sum */ public int maxSubarray4(int[] nums, int k) { // Write your code here if (nums == null || nums.length < k) { return 0; } int max = 0; int[] sum = new int[nums.length + 1]; sum[0] = 0; for (int i = 0; i < k; i++) { max += nums[i]; } int min_prefix = 0; for (int i = 1; i <= nums.length; i++) { sum[i] = sum[i - 1] + nums[i - 1]; if (i >= k && sum[i] - min_prefix > max) { max = sum[i] - min_prefix; } if (i >= k) { min_prefix = Math.min(min_prefix, sum[i - k + 1]); } } return max; } }
注意:使用max变量记录满足长度要求的子数组的最大和,sum变量记录数组和,min_prefix变量记录满足长度要求数组的前面子数组的最小和。在for循环的过程中,sum值逐渐累加,当i≥k(长度要求)之后,max取当前max和sum-min_prefix的较大值,min_prefix取当前min_prefix和sum[i-k+1]的较小值。最后返回max值。
21.maximum-subarray-v(最大子数组V)【hard】
给定一个整数数组,找到一个连续的子阵列,其具有最大和, 且长度应在k1和k2之间(包括k1和k2)。返回最大的和,如果数组中的元素少于k1个,则返回0。

public class Solution { /** * @param nums an array of integers * @param k1 an integer * @param k2 an integer * @return the largest sum */ public int maxSubarray5(int[] nums, int k1, int k2) { // Write your code here int n = nums.length; if (n < k1) { return 0; } int result = Integer.MIN_VALUE; int[] sum = new int[n + 1]; sum[0] = 0; LinkedList<Integer> queue = new LinkedList<Integer>(); for (int i = 1; i <= n; ++i) { sum[i] = sum[i - 1] + nums[i - 1]; if (!queue.isEmpty() && queue.getFirst() < i - k2) { queue.removeFirst(); } if (i >= k1) { while (!queue.isEmpty() && sum[queue.getLast()] > sum[i - k1]) { queue.removeLast(); } queue.add(i - k1); } // [i - k2, i - k1] if (!queue.isEmpty() && sum[i] - sum[queue.getFirst()] > result) { result = Math.max(result, sum[i] - sum[queue.getFirst()]); } } return result; } }
22.maximum-subarray-difference(最大子数组差)
给定一个整数数组,找出两个不重叠的子数组A和B,使两个子数组和的差的绝对值|SUM(A) - SUM(B)|最大。返回这个最大的差值。子数组最少包含一个数。

public class Solution { /** * @param nums: A list of integers * @return: An integer indicate the value of maximum difference between two * Subarrays */ public int maxDiffSubArrays(int[] nums) { // write your code here int size = nums.length; int[] left_max = new int[size]; int[] left_min = new int[size]; int[] right_max = new int[size]; int[] right_min = new int[size]; int[] copy = new int[size]; for (int i = 0; i < size; i++) { copy[i] = -1 * nums[i]; } int sum = 0; int minSum = 0; int max = Integer.MIN_VALUE; for (int i = 0; i < size; i++) { sum += nums[i]; max = Math.max(max, sum - minSum); minSum = Math.min(minSum, sum); left_max[i] = max; } sum = 0; minSum = 0; max = Integer.MIN_VALUE; for (int i = size - 1; i >= 0; i--) { sum += nums[i]; max = Math.max(max, sum - minSum); minSum = Math.min(minSum, sum); right_max[i] = max; } sum = 0; minSum = 0; max = Integer.MIN_VALUE; for (int i = 0; i < size; i++){ sum += copy[i]; max = Math.max(max, sum - minSum); minSum = Math.min(sum, minSum); left_min[i] = -1 * max; } sum = 0; minSum = 0; max = Integer.MIN_VALUE; for (int i = size - 1; i >= 0; i--){ sum += copy[i]; max = Math.max(max, sum - minSum); minSum = Math.min(sum, minSum); right_min[i] = -1 * max; } int diff = 0; for (int i = 0; i < size - 1; i++) { diff = Math.max(diff, Math.abs(left_max[i] - right_min[i + 1])); diff = Math.max(diff, Math.abs(left_min[i] - right_max[i + 1])); } return diff; } }
注意:求出某个位置往左最大子数组和left_max[]与最小子数组和left_min[], 某个位置往右的最大子数组和right_max[]与最小子数组和right_min[]。
left_min[]和right_min[]的求法:原数组的相反数组(每个元素*-1)求最大和,结果取反(*-1)即为原数组的最小和。某个位置往右的子数组和要倒序进行。
因为两个subarray 一定不重叠,所以必定存在一条分割线分开这两个 subarrays,所以 最后的部分里:
int diff = 0;
for (int i = 0; i < size - 1; i++) {
diff = Math.max(diff, Math.abs(left_max[i] - right_min[i + 1]));
diff = Math.max(diff, Math.abs(left_min[i] - right_max[i + 1]));
}
return diff;枚举这条分割线的位置。
23.window-sum(滑动窗口内数的和)
给定一个包含n个整数的数组和一个滑动窗口(大小为k),从数组的开头逐个移动窗口,在每次移动时找到窗口内的元素的总和。
例:数组[1,2,7,8,5]
, 滑动窗口大小 k = 3。
1 + 2 + 7 = 10,2 + 7 + 8 = 17,7 + 8 + 5 = 20。返回[10,17,20]。

public class Solution { /** * @param nums a list of integers. * @return the sum of the element inside the window at each moving. */ public int[] winSum(int[] nums, int k) { // write your code here if (nums == null || nums.length < k || k <= 0) { return new int[0]; } int[] sums = new int[nums.length - k + 1]; for (int i = 0; i < k; i++) { sums[0] += nums[i]; } for (int i = 1; i < sum.length; i++) { sums[i] = sums[i - 1] - nums[i - 1] + nums[i + k - 1]; } return sums; } }
注意:声明sum数组的长度为nums.length - k + 1。首先计算sum[0],然后根据sum[i] = sum[i - 1] - nums[i - 1] + nums[i + k - 1]计算其余值。
24.subarray-sum(子数组之和)
给定一个整数数组,找到和为零的子数组。你的代码应该返回满足要求的子数组的起始位置和结束位置。至少有一个子数组,它的和等于零。

public class Solution { /** * @param nums: A list of integers * @return: A list of integers includes the index of the first number * and the index of the last number */ public ArrayList<Integer> subarraySum(int[] nums) { // write your code here ArrayList<Integer> result = new ArrayList<Integer>(); HashMap<Integer, Integer> map = new HashMap<Integer, Integer>(); int sum = 0; map.put(0, -1); for (int i = 0; i < nums.length; i++) { sum += nums[i]; if (map.containsKey(sum)) { result.add(map.get(sum) + 1); result.add(i); return result; } else { map.put(sum, i); } } return result; } }
注意:使用HashMap存储<当前数组和,当前位置>。首先存入<0, -1>,等再次遇见数组和为0时,取出上次和为0的位置加1,和当前位置一起返回即可。
25.subarray-sum-closest(最接近零的子数组和)
给定一个整数数组,找到一个和最接近于零的子数组。你的代码应该返回满足要求的子数组的起始位置和结束位置。

public class Solution { /** * @param nums: A list of integers * @return: A list of integers includes the index of the first number * and the index of the last number */ class Pair { int sum; int index; public Pair(int sum, int index) { this.sum = sum; this.index = index; } } public int[] subarraySumClosest(int[] nums) { // write your code here int[] res = new int[2]; if (nums == null || nums.length == 0) { return res; } int len = nums.length; if (len == 1) { res[0] = 0; res[1] = 0; return res; } Pair[] sums = new Pair[len + 1]; sums[0] = new Pair(0, 0); for (int i = 1; i <= len; i++) { sums[i] = new Pair(sums[i - 1].sum + nums[i - 1], i); } Arrays.sort(sums, new Comparator<Pair>() { public int compare(Pair a, Pair b) { return a.sum - b.sum; } }); int min = Integer.MAX_VALUE; for (int i = 1; i <= len; i++) { if (min > sums[i].sum - sums[i - 1].sum) { min = sums[i].sum - sums[i - 1].sum; int[] temp = new int[]{sums[i].index - 1, sums[i - 1].index - 1}; Arrays.sort(temp); res[0] = temp[0] + 1; res[1] = temp[1]; } } return res; } }
注意:把“和”转换成“差”,题干里是说看哪几个数的和绝对值最小,很容易想到暴力遍历。但是换一种思路,当我们把数组里的数依次加起来,连同坐标存起来,那么想看和的绝对值,其实就是看存起来的那些数之间的差(不要忘记第一个数也可以独立成一个子数组,所以前面先设个(0,0))。使用class Pair{int sum; int index}记录当前数组和与当前位置坐标,声明一个Pair类型的数组sums[]。然后就是排序,对sums排好序之后,相邻的差最小也就是绝对值和最小。
求绝对值和最小的位置:
int min = Integer.MAX_VALUE;
for (int i = 1; i <= len; i++) {
if (min > sums[i].sum - sums[i - 1].sum) {
min = sums[i].sum - sums[i - 1].sum;
int[] temp = new int[]{sums[i].index - 1, sums[i - 1].index - 1};//index-1的原因是前面存储的时候i从1开始到len,而实际坐标从0开始到len-1。
Arrays.sort(temp);
res[0] = temp[0] + 1;//+1是因为题目要求返回子数组的起始位置,而此时的temp[0]是上一个数组的结束位置。
res[1] = temp[1];
}
}
排序操作:
Arrays.sort(数组名, new Comparator<Pair>() {
public int compare(Pair a, Pair b) {
return a.sum - b.sum;//前者-后者,从小到大排序
//return b.sum - a.sum;h后者- 前者,从大到小排序
}
});
问:为什么需要一个 (0,0) 的初始 Pair?
答:我们首先需要回顾一下,在subarray这节课里,我们讲过一个重要的知识点,叫做 PrefixSum。比如对于数组 [1,2,3,4],它的PrefixSum 是 [1,3,6,10]分别表示 前1个数之和,前2个数之和,前3个数之和,前4个数之和。这个时候如果你想要知道 子数组 从下标 1 到下标 2 的这一段的和(2+3),就用前3个数之和 减去 前1个数之和 = PrefixSum[2] - PrefixSum[0] = 6 - 1 = 5。你可以看到这里的 前 x 个数 和具体对应的下标之间存在 +-1 的关系, 第 x 个数的下标是 x - 1,反之下标 x 是第 x + 1 个数。
那么问题来了,如果要计算 下标从 0~2 这一段呢?也就是第1个数到第3个数,因为那样会访问到 PrefixSum[-1]。所以我们把 PrefixSum 整体往后面移动一位,把第0位空出来表示前0个数之和,也就是0. => [0,1,3,6,10] 。那么此时就用 PrefixSum[3] - PrefixSum[0] ,这样计算就更方便了。此时,PrefixSum[i] 代表 前i个数之和,也就是 下标区间在 0 ~ i-1 这一段的和。那么回过头来看看,为什么我们需要一个 (0,0) 的 pair 呢?因为 这个 0,0 代表的就是前0个数之和为0 一个 n个数的数组, 变成了 prefix Sum 数组之后,会多一个数出来。
26.maximum-product-subarray(乘积最大子序列)
找出一个序列中乘积最大的连续子序列(至少包含一个数)。

public class Solution { /** * @param nums: an array of integers * @return: an integer */ public int maxProduct(int[] nums) { // write your code here int[] max = new int[nums.length]; int[] min = new int[nums.length]; max[0] = nums[0]; min[0] = nums[0]; int result = nums[0]; for (int i = 1; i < nums.length; i++) { max[i] = nums[i]; min[i] = nums[i]; if (nums[i] > 0) { max[i] = Math.max(max[i], max[i - 1] * nums[i]); min[i] = Math.min(min[i], min[i - 1] * nums[i]); } else if (nums[i] < 0) { max[i] = Math.max(max[i], min[i - 1] * nums[i]); min[i] = Math.min(min[i], max[i - 1] * nums[i]); } result = Math.max(result, max[i]); } return result; } }
注意:考虑到序列中的元素有正有负,如果下一个元素是正数,则当前乘积越大,相乘之后积越大。如果下一个元素是负数,则当前乘积越小,相乘以后积越大。所以使用max[]和min[]分别记录当前位置乘积的最大和最小值。同时要注意在每次循环开始时max[i]=min[i]=nums[i]。
2.排序数组Sorted Array
27.merge-two-sorted-arrays(合并排序数组)
合并两个排序的整数数组A和B变成一个新的数组。
方法一(从前往后合并):

class Solution { /** * @param A and B: sorted integer array A and B. * @return: A new sorted integer array */ public int[] mergeSortedArray(int[] A, int[] B) { // Write your code here if (A == null || B == null) { return null; } int[] result = new int[A.length + B.length]; int i = 0; int j = 0; int index = 0; while (i < A.length && j < B.length) { if (A[i] < B[j]) { result[index++] = A[i++]; } else { result[index++] = B[j++]; } } while (i < A.length) { result[index++] = A[i++]; } while (j < B.length) { result[index++] = B[j++]; } return result; } }
注意:经典合并法。当(while)两个数组都不为空时,比较节点值的大小...当(while)其中一个数组不为空时...
方法二(从后往前合并):

int len1 = A.length; int len2 = B.length; int[] arr = new int[len1 + len2]; int end1 = len1 - 1; int end2 = len2 - 1; int index = arr.length - 1; while (end1 >= 0 && end2 >= 0) { if (A[end1] > B[end2]) { arr[index] = A[end1]; end1--; index--; } else { arr[index] = B[end2]; end2--; index--; } } while (end1 >= 0) { arr[index] = A[end1]; end1--; index--; } while (end2 >= 0) { arr[index] = B[end2]; end2--; index--; }
28.merge-sorted-array(合并排序数组II)
合并两个排序的整数数组A和B变成一个新的数组。你可以假设A具有足够的空间(A数组的大小大于或等于m+n)去添加B中的元素。

class Solution { /** * @param A: sorted integer array A which has m elements, * but size of A is m+n * @param B: sorted integer array B which has n elements * @return: void */ public void mergeSortedArray(int[] A, int m, int[] B, int n) { // write your code here int i = m - 1; int j = n - 1; int index = m + n - 1; while (i >= 0 && j >= 0) { if (A[i] > B[j]) { A[index--] = A[i--]; } else { A[index--] = B[j--]; } } while (i >= 0) { A[index--] = A[i--]; } while (j >= 0) { A[index--] = B[j--]; } } }
注意:与28题基本思路相同,但是合并顺序为从后往前。
29.intersection-of-two-arrays(两数组的交)
返回两个数组的交。结果中的每个元素必须是唯一的。结果可以是任何顺序。
HashSet解法:

public class Solution { /** * @param nums1 an integer array * @param nums2 an integer array * @return an integer array */ public int[] intersection(int[] nums1, int[] nums2) { // Write your code here if (nums1 == null || nums2 == null) { return null; } HashSet<Integer> set = new HashSet<Integer>(); for (int i = 0; i < nums1.length; i++) { set.add(nums1[i]); } HashSet<Integer> resultSet = new HashSet<Integer>(); for (int i = 0; i < nums2.length; i++) { if (set.contains(nums2[i]) && !resultSet.contains(nums2[i])) { resultSet.add(nums2[i]); } } int length = resultSet.size(); int[] result = new int[length]; int i = 0; for (Integer num : resultSet) { result[i++] = num; } return result; } }
注意:把A数组中的元素都存入set(set中的元素不重复),把包含在set中且resultset中没有的B数组中的元素放入resultset,返回resultset转换成的整数数组。
HashSet<Integer> resultSet = new HashSet<Integer>(); int length = resultSet.size(); int[] result = new int[length]; int i = 0; for (Integer num : resultSet) {result[i++] = num; }
30.intersection-of-two-arrays-ii(两数组的交II)
计算两个数组的交。每个元素出现的次数和在数组里一样。答案可以以任意顺序给出。

public class Solution { /** * @param nums1 an integer array * @param nums2 an integer array * @return an integer array */ public int[] intersection(int[] nums1, int[] nums2) { // Write your code here Map<Integer, Integer> map = new HashMap<Integer, Integer>(); for (int i = 0; i < nums1.length; i++) { if (map.containsKey(nums1[i])) { map.put(nums1[i], map.get(nums1[i]) + 1); } else { map.put(nums1[i], 1); } } List<Integer> results = new ArrayList<Integer>(); for (int i = 0; i < nums2.length; i++) { if (map.containsKey(nums2[i]) && map.get(nums2[i]) > 0) { results.add(nums2[i]); map.put(nums2[i], map.get(nums2[i]) - 1); } } int length = results.size(); int[] result = new int[length]; for (int i = 0; i < results.size(); ++i) { result[i] = results.get(i); } return result; } }
注意:把A数组中的元素都存入map<元素值,元素个数>,把包含在map中且results中没有的B数组中的元素放入results(map中该元素个数减一),返回results转换成的整数数组。
List<Integer> results = new ArrayList<Integer>(); int length = resultSet.size(); int[] result = new int[length]; for (int i = 0; i < results.size(); ++i) {result[i] = results.get(i);}
31.median-of-two-sorted-array(两个排序数组的中位数)【hard】【要记住】
两个排序的数组A和B分别含有m和n个数,找到两个排序数组的中位数,要求时间复杂度应为O(log (m+n))。
方法一:

class Solution { /** * @param A: An integer array. * @param B: An integer array. * @return: a double whose format is *.5 or *.0 */ public double findMedianSortedArrays(int[] A, int[] B) { // write your code here int lenA = A.length; int lenB = B.length; int[] arr = new int[lenA + lenB]; int endA = lenA - 1; int endB = lenB - 1; int index = lenA + lenB - 1; while (endA >= 0 && endB >= 0) { if (A[endA] > B[endB]) { arr[index] = A[endA]; endA--; index--; } else { arr[index] = B[endB]; endB--; index--; } } while (endA >= 0) { arr[index] = A[endA]; endA--; index--; } while (endB >= 0) { arr[index] = B[endB]; endB--; index--; } if (arr.length % 2 == 0) { return (arr[arr.length / 2] + arr[arr.length / 2 - 1]) / 2.0; } else { return arr[arr.length / 2]; } }
注意:时间复杂度和空间复杂度都是O(m + n),由于两个数组都是有序的,要找到中位数最好的方法就是将两个有序数组进行合并,创建一个大小为m+n的数组arr, 由后向前遍历,比较两个数组末尾元素的大小,将大的那个数存放到arr数组的尾部,然后大数数组的尾部下标向前移动,arr尾部下标也向前移动。直到某一个数组已经全部放进arr中,将剩下的那个数组全部拷贝进去,最后判断如果是奇数,则返回中间位的那个数,如果是偶数,返回中间两个数的平均数。
方法二:

class Solution { /** * @param A: An integer array. * @param B: An integer array. * @return: a double whose format is *.5 or *.0 */ public double findMedianSortedArrays(int[] A, int[] B) { // write your code here int len = A.length + B.length; if (len % 2 == 1) { return findKth(A, 0, B, 0, len / 2 + 1); } else { return ( findKth(A, 0, B, 0, len / 2) + findKth(A, 0, B, 0, len / 2 + 1) ) / 2.0; } } public static int findKth(int[] A, int A_start, int[] B, int B_start, int k){ //较短序列所有元素都被抛弃,则返回较长序列的第k个元素(在数组中下标是k-1) if (A_start >= A.length) { return B[B_start + k - 1]; } if (B_start >= B.length) { return A[A_start + k - 1]; } //k==1表示已经找到第k-1小的数,则第k个数为两个数组开始时的较小值 if (k == 1) { return Math.min(A[A_start], B[B_start]); } //A_key和B_key记录当前要比较的A和B数组中的第k/2个元素 int A_key = A_start + k / 2 - 1 < A.length ? A[A_start + k / 2 - 1] : Integer.MAX_VALUE; int B_key = B_start + k / 2 - 1 < B.length ? B[B_start + k / 2 - 1] : Integer.MAX_VALUE; //如果数组A中第k/2个元素小于B中第k/2个元素,我们不确定B中第k/2个元素是大了还是小了,但A中的前k/2个元素肯定都小于目标,所以我们将A中前k/2个元素全部抛弃,形成一个较短的新序列。然后,用新序列替代原先的第一个序列,再找其中的第k-k/2个元素(因为我们已经排除了k/2个元素,k需要更新为k-k/2),依次递归。 if (A_key < B_key) { return findKth(A, A_start + k / 2, B, B_start, k - k / 2); } else { return findKth(A, A_start, B, B_start + k / 2, k - k / 2); } } }
注意:时间复杂度是O(log(m+n)),一般来说都是分治法或者二分搜索。首先我们先分析下题目,假设两个有序序列共有n个元素(根据中位数的定义我们要分两种情况考虑),当n为奇数时,搜寻第(n/2+1)个元素,当n为偶数时,搜寻第(n/2+1)和第(n/2)个元素,然后取他们的均值。进一步的,我们可以把这题抽象为“搜索两个有序序列的第k个元素”。如果我们解决了这个k元素问题,那中位数不过是k的取值不同罢了。
那如何搜索两个有序序列中第k个元素呢,这里又有个技巧。假设序列都是从小到大排列,对于第一个序列中前p个元素和第二个序列中前q个元素,我们想要的最终结果是:p+q等于k-1,且一序列第p个元素和二序列第q个元素都小于总序列第k个元素。因为总序列中,必然有k-1个元素小于等于第k个元素。这样第p+1个元素或者第q+1个元素就是我们要找的第k个元素。所以,我们可以通过二分法将问题规模缩小,假设p=k/2-1,则q=k-p-1,且p+q=k-1。如果第一个序列第p个元素小于第二个序列第q个元素,我们不确定二序列第q个元素是大了还是小了,但一序列的前p个元素肯定都小于目标,所以我们将第一个序列前p个元素全部抛弃,形成一个较短的新序列。然后,用新序列替代原先的第一个序列,再找其中的第k-p个元素(因为我们已经排除了p个元素,k需要更新为k-p),依次递归。(每次递归不仅要更新数组起始位置(起始位置之前的元素被抛弃),也要更新k的大小(扣除被抛弃的元素))同理,如果第一个序列第p个元素大于第二个序列第q个元素,我们则抛弃第二个序列的前q个元素。递归的终止条件有如下几种:
-
较短序列所有元素都被抛弃,则返回较长序列的第k个元素(在数组中下标是k-1)
-
一序列第p个元素等于二序列第q个元素,此时总序列第p+q=k-1个元素的后一个元素,也就是总序列的第k个元素