1. 程式人生 > >九章演算法筆記 6.連結串列與陣列 Linked List & Array

九章演算法筆記 6.連結串列與陣列 Linked List & Array

刷題注意事項 cs3k.com

每道題需要總結的

  1. 思路
  2. 演算法
  3. 核心程式碼
  4. 這個題得到的啟示!!!重點是bug free的能力

 

linked list理解

enter image description here

結果兩個都是 1 2 3

  1. node是存在main函式裡的區域性變數, 還是全域性變數?

區域性

  1. node1 是一個指標, 在32位即中佔有4個位元組. 也可以理解為引用, 存的是一個記憶體的地址. 所以一個ListNode佔8個位元組, val佔4個, next佔四個.

可以理解為, 記憶體是個大陣列, ref和pointer都是陣列的下標, 是index

enter image description here

Reverse Nodes in k-Groups

Given a linked list, reverse the nodes of a linked list k at a time and return its modified list.

If the number of nodes is not a multiple of k then left-out nodes in the end should remain as it is.

You may not alter the values in the nodes, only nodes itself may be changed.

Only constant memory is allowed.

複雜問題的解決方案

cs3k.com

複雜的, 不能一眼看到結果的, 要拆分.

拆分就是要步驟化, 先框架, 再細節.

複雜的問題通過一個for迴圈, 一個while迴圈, 或者一個123步怎麼做, 變成一個更小的問題.

每個function需要明確

dummy node

java要new, c++不要new,c++如果new了要刪除

HEAD = DUMMY 這句話總是需要麼?

幾乎

什麼時候使用 DUMMY NODE?

連結串列結構發生變化的時候

DUMMY NODE 是否需要刪除?

不需要

使用 DUMMY NODE 算面試官會說我耗費了額外空間麼?

他沒那麼神精病, O(1)的額外空間不算額外空間,O(n)才算

DUMMY NODE 非用不可麼?

不是, 但是用了程式碼比較簡潔

DUMMY NODE 初始化的值重要麼?

不重要, 只需要它的next的值

enter image description here

enter image description here

Copy List with Random Pointer

cs3k.com

A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null.

Return a deep copy of the list.

類似clone graph, 其中clone graph的步驟是:

  1. 找所有的點
  2. 把所有的點複製一遍
  3. 把所有的邊複製一遍

hash map solution

連同老節點和新節點.

public class Solution {
    public RandomListNode copyRandomList(RandomListNode head) { if (head == null) { return null; } HashMap<RandomListNode, RandomListNode> map = new HashMap<RandomListNode, RandomListNode>(); RandomListNode dummy = new RandomListNode(0); RandomListNode pre = dummy, 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; } }

no extra space solution

神馬是extra space?

除了input 和output以外的額外空間叫extra space

  1. copy list 翻倍

1->2->3->4

=>

1->1’->2->2’->3->3’->4->4’->NULL

  1. copy random 連結

A.next.random = A.random.next

  1. 分開
/*第一遍掃的時候巧妙運用next指標, 開始陣列是1->2->3->4  。 然後掃描過程中 先建立copy節點 1->1`->2->2`->3->3`->4->4`, 然後第二遍copy的時候去建立邊的copy, 拆分節點, 一邊掃描一邊拆成兩個連結串列,這裡用到兩個dummy node。第一個連結串列變回  1->2->3 , 然後第二變成 1`->2`->3`  */
//No HashMap version
public class Solution { private void copyNext(RandomListNode head) { while (head != null) { RandomListNode newNode = new RandomListNode(head.label); newNode.random = head.random; newNode.next = head.next; 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) { if (head == null) { return null; } copyNext(head); copyRandom(head); return splitList(head); } } 

Linked List Cycle

cs3k.com

兩種問法

  1. 有環
  2. 環入口

快慢指標

enter image description here

小細節

  1. null pointer checking驗證xx.xxy一定要檢測不空
  2. 陣列檢測是否越界,再取它的值
public class Solution {
    public Boolean hasCycle(ListNode head) { if (head == null || head.next == null) { return false; } ListNode fast, slow; fast = head.next; slow = head; while (fast != slow) { if(fast==null || fast.next==null) return false; fast = fast.next.next; slow = slow.next; } return true; } }

Linked List Cycle II

cs3k.com

 

我的理解:

1.環如圖,環外長度n用黃綠色表示,環長度m,用粉色表示如pic1。 1 2.因為s一次一步,f一次兩步,所以s每走一步,f都比s多走一步。所以當慢指標s走到n的時候,f在他前面n步,如pic2。 2   3.現階段f已經在s前面n步了。兩個人離口圈還差m-n步,m-n長度用藍色表示。如果f想追上s,需要比s多走m-n步。又因為f比s多走的步數和s走的一樣多(s走1步,f比他多走1步;s走2步,f比它多走2步;以此類推)。所以當s再走m-n步的時候,s和f相遇如pic3. 3 4.pic3的時候,是s從交點O走出來m-n步的時候,所以s想走到O,還需要m-(m-n)即n步。此時再放一個慢指標s2在起點,當s和s2都一次一步的走的時候,他們同事走完黃綠色的n的長度,在交點O相遇。Bingo snipaste_20171017_114354

網上別人的偏數學的解釋,參考LeetCode Discuss(https://leetcode.com/discuss/16567/concise-solution-usi:

Linked List Cycle II

1). 使用快慢指標法,若連結串列中有環,可以得到兩指標的交點M

2). 記連結串列的頭節點為H,環的起點為E

2.1) L1為H到E的距離
2.2) L2為從E出發,首次到達M時的路程
2.3) C為環的周長
2.4) n為快慢指標首次相遇時,快指標在環中繞行的次數

根據L1,L2和C的定義,我們可以得到:

慢指標行進的距離為L1 + L2

快指標行進的距離為L1 + L2 + n * C

由於快慢指標行進的距離有2倍關係,因此:

2 * (L1+L2) = L1 + L2 + n * C => L1 + L2 = n * C => L1 = (n – 1)* C + (C – L2)

可以推出H到E的距離 = 從M出發繞環到達E時的路程

因此,當快慢指標在環中相遇時,我們再令一個慢指標從頭節點出發

接下來當兩個慢指標相遇時,即為E所在的位置

實在不明白,就背下來吧。。。

snipaste_20171012_153807

Intersection of 2 linked list

enter image description here

L1走到頭,然後把尾巴接到L2上, 然後liked list cycle II

 

public class Solution {
    /** * @param headA: the first list * @param headB: the second list * @return: a ListNode */ public ListNode getIntersectionNode(ListNode headA, ListNode headB) { if (headA == null || headB == null) { return null; } // get the tail of list A. ListNode node = headA; while (node.next != null) { node = node.next; } node.next = headB; ListNode result = listCycleII(headA); node.next = null; return result; } private ListNode listCycleII(ListNode head) { ListNode slow = head, fast = head.next; while (slow != fast) { if (fast == null || fast.next == null) { return null; } slow = slow.next; fast = fast.next.next; } slow = head; fast = fast.next; while (slow != fast) { slow = slow.next; fast = fast.next; } return slow; }

Sort List

cs3k.com

Sort a linked list in O(n log n) time using constant space complexity.

空間複雜度相關

constant space

O(1) memory

no extra space

三者一樣

基於比較的排序,最優的時間複雜度也是nlogn

O(n) bucket sort

O(n * n^(1/2)) shell sort

O(nlogn) quick merge heap

radix sort 和counting sort(數數有幾個1 有幾個2 再用hash??)

這兩個是基於value的排序, 要求key可數, float double啥的就不行了

space complexity

quick: O(1)

merge: O(n) 一定要倒騰出來,再挪回去,所以需要其他的n個位置

heap:O(1)/O(n) 取決於用不用priority queue

list vs array

list:靈活,可打亂,可重組

array:下標連續,整塊記憶體

ps:quick sort和merge sort都自己實現下

// version 1: Merge Sort
public class Solution { private ListNode findMiddle(ListNode head) { ListNode slow = head, fast = head.next; while (fast != null && fast.next != null) { fast = fast.next.next; slow = slow.next; } return slow; } private ListNode merge(ListNode head1, ListNode head2) { ListNode dummy = new ListNode(0); ListNode tail = dummy; while (head1 != null && head2 != null) { if (head1.val < head2.val) { tail.next = head1; head1 = head1.next; } else { tail.next = head2; head2 = head2.next; } tail = tail.next; } if (head1 != null) { tail.next = head1; } else { tail.next = head2; } return dummy.next; } public ListNode sortList(ListNode head) { 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); } } // version 2: Quick Sort 1 public class Solution { public ListNode sortList(ListNode head) { if (head == null || head.next == null) { return head; } ListNode mid = findMedian(head); // O(n) ListNode leftDummy = new ListNode(0), leftTail = leftDummy; ListNode rightDummy = new ListNode(0), rightTail = rightDummy; ListNode middleDummy = new ListNode(0), 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); } private ListNode findMedian(ListNode head) { ListNode slow = head, 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), 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; } } // version 3: Quick Sort 2 /** * Definition for ListNode. * public class ListNode { * int val; * ListNode next; * ListNode(int val) { * this.val = val; * this.next = null; * } * } */ class Pair { public ListNode first, second; public Pair(ListNode first, ListNode second) { this.first = first; this.second = second; } } 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. */ public ListNode sortList(ListNode head) { if (head == null || head.next == null) { return head; } ListNode mid = findMedian(head); // O(n) Pair pair = partition(head, mid.val); // O(n) ListNode left = sortList(pair.first); ListNode right = sortList(pair.second); getTail(left).next = right; // O(n) return left; } // 1->2->3 return 2 // 1->2 return 1 private ListNode findMedian(ListNode head) { ListNode slow = head, fast = head.next; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } return slow; } // < value in the left, > value in the right private Pair partition(ListNode head, int value) { ListNode leftDummy = new ListNode(0), leftTail = leftDummy; ListNode rightDummy = new ListNode(0), rightTail = rightDummy; ListNode equalDummy = new ListNode(0), equalTail = equalDummy; while (head != null) { if (head.val < value) { leftTail.next = head; leftTail = head; } else if (head.val > value) { rightTail.next = head; rightTail = head; } else { equalTail.next = head; equalTail = head; } head = head.next; } leftTail.next = null; rightTail.next = null; equalTail.next = null; if (leftDummy.next == null && rightDummy.next == null) { ListNode mid = findMedian(equalDummy.next); leftDummy.next = equalDummy.next; rightDummy.next = mid.next; mid.next = null; } else if (leftDummy.next == null) { leftTail.next = equalDummy.next; } else { rightTail.next = equalDummy.next; } return new Pair(leftDummy.next, rightDummy.next); } private ListNode getTail(ListNode head) { if (head == null) { return null; } while (head.next != null) { head = head.next; } return head; } }

Merge Sorted Array

cs3k.com

Given two sorted integer arrays A and B, merge B into A as one sorted array.

從後往前排

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) { int i = m-1, j = n-1, 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--]; } } }

merge two sorted arrays

每次比較, 誰小誰出列的反向, 誰大誰出列

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) { if (A == null || B == null) { return null; } int[] result = new int[A.length + B.length]; int i = 0, j = 0, 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; } }

Intersection of Two Arrays

cs3k.com

Given two arrays, write a function to compute their intersection.

enter image description here

1.雜湊表 小的塞雜湊表

2.小的出去

相同的都出去, 加到新的佇列裡

3.排小的

for打的的每個數, 二分查n

// version 1: sort & merge
public class Solution { /** * @param nums1 an integer array * @param nums2 an integer array * @return an integer array */ public int[] intersection(int[] nums1, int[] nums2) { Arrays.sort(nums1); Arrays.sort(nums2); int i = 0, j = 0; int[] temp = new int[nums1.length]; int index = 0; while (i < nums1.length && j < nums2.length) { if (nums1[i] == nums2[j]) { if (index == 0 || temp[index - 1] != nums1[i]) { temp[index++] = nums1[i]; } i++; j++; } else if (nums1[i] < nums2[j]) { i++; } else { j++; } } int[] result = new int[index]; for (int k = 0; k < index; k++) { result[k] = temp[k]; } return result; } } // version 2: hash map public class Solution { /** * @param nums1 an integer array * @param nums2 an integer array * @return an integer array */ public int[] intersection(int[] nums1, int[] nums2) { if (nums1 == null || nums2 == null) { return null; } HashSet hash = new HashSet<>(); for (int i = 0; i < nums1.length; i++) { hash.add(nums1[i]); } HashSet resultHash = new HashSet<>(); for (int i = 0; i < nums2.length; i++) { if (hash.contains(nums2[i]) && !resultHash.contains(nums2[i])) { resultHash.add(nums2[i]); } } int size = resultHash.size(); int[] result = new int[size]; int index = 0; for (Integer num : resultHash) { result[index++] = num; } return result; } } // version 3: sort & binary search public class Solution { /** * @param nums1 an integer array * @param nums2 an integer array * @return an integer array */ public int[] intersection(int[] nums1, int[] nums2) { if (nums1 == null || nums2 == null) { return null; } HashSet set = new HashSet<>(); Arrays.sort(nums1); for (int i = 0; i < nums2.length; i++) { if (set.contains(nums2[i])) { continue; } if (binarySearch(nums1, nums2[i])) { set.add(nums2[i]); } } int[] result = new int[set.size()]; int index = 0; for (Integer num : set) { result[index++] = num; } return result; } private boolean binarySearch(int[] nums, int target) { if (nums == null || nums.length == 0) { return false; } int start = 0, end = nums.length - 1; while (start + 1 < end) { int mid = (end - start) / 2 + start; if (nums[mid] == target) { return true; } if (nums[mid] < target) { start = mid; } else { end = mid; } } if (nums[start] == target) { return true; } if (nums[end] == target) { return true; } return false; } }

Median of two Sorted Arrays

There are two sorted arrays A and B of size m and n respectively. Find the median of the two sorted arrays.

聯想到quick select那道題, 其中k = median

就merge k= (n+m/2)次, 但是不夠快

如果O(k)不夠快,只能往logn方向走

但是兩個陣列的二分, 不好分, 所以想到縮小一倍問題的size

所以在一個數組裡刪掉2/k個數

終止的三個條件

cs3k.com

A沒了

B沒了

k= 1

public class Solution {
    public double findMedianSortedArrays(int A[], int B[]) { int len = A.length + B.length; if (len % 2 == 1) { return findKth(A, 0, B, 0, len / 2 + 1); } return ( findKth(A, 0, B, 0, len / 2) + findKth(A, 0, B, 0, len / 2 + 1) ) / 2.0; } // find kth number of two sorted array public static int findKth(int[] A, int A_start, int[] B, int B_start, int k){ if (A_start >= A.length) { return B[B_start + k - 1]; } if (B_start >= B.length) { return A[A_start + k - 1]; } if (k == 1) { return Math.min(A[A_start], B[B_start]); } 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; 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); } } }

Maximum Subarray

cs3k.com

Given an array of integers, find a contiguous subarray which has the largest sum.

求陣列中間的一段, 就長減短

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]

類似的closest就拍個序

// Version 1: Greedy

public class Solution { public int maxSubArray(int[] A) { if (A == null || A.length == 0){ return 0; } int max = Integer.MIN_VALUE, sum = 0; for (int i = 0; i < A.length; i++) { sum += A[i]; max = Math.max(max, sum); sum = Math.max(sum, 0); } return max; } } // Version 2: Prefix Sum public class Solution { public int maxSubArray(int[] A) { if (A == null || A.length == 0){ return 0; } int max = Integer.MIN_VALUE, sum = 0, minSum = 0; for (int i = 0; i < A.length; i++) { sum += A[i]; max = Math.max(max, sum - minSum); minSum = Math.min(minSum, sum); } return max; } } public class Solution { /** * @param nums: a list of integers * @return: A integer indicate the sum of minimum subarray */ public int maxSubArray(ArrayList nums) { // write your code if(nums.size()==0) return 0; int n = nums.size(); int []global = new int[n]; int []local = new int[n]; global[0] = nums.get(0); local[0] = nums.get(0); for(int i=1;i<n;i++) { local[i] = Math.max(nums.get(i),local[i-1]+nums.get(i)); global[i] = Math.max(local[i],global[i-1]); } return global[n-1]; } }

Subarray Sum Closest

cs3k.com

Given an integer array, find a subarray with sum closest to zero. Return the indexes of the first number and last number.

class Solution {
public:
    /** * @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 */ struct node { //成員,生成,比較 node(int _value, int _pos):value(_value), pos(_pos) {} int value, pos; //重點有二,一個是&,一個是const bool operator<(const node &o) const{ return (value < o.value || value == o.value && pos < o.pos); } }; vector<int> subarraySumClosest(vector<int> nums){ // write your code here vector<node> s; vector<int> results(2); s.push_back(node(0,-1)); int sum = 0, len = nums.size(); for (int i = 0; i < len ; ++i) { sum += nums[i]; s.push_back(node(sum, i)); } sort(s.begin(), s.end()); len = s.size(); int ans = 0x7fffffff; for (int i = 0; i < len-1; ++i) if (abs(s[i+1].value - s[i].value) < ans) { ans = abs(s[i+1].value - s[i].value); results[0] = min(s[i].pos, s[i+1].pos)+1; results[1] = max(s[i].pos, s[i+1].pos); } return results; } };