左神算法入门笔记

左神算法入门笔记

0.特殊

0.1 异或

0.1.1 用异或做交换

void swap(int& a, int& b){
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
}
  • 原理:
    • N ^ N == 0
    • N ^ 0 == N
    • (A ^ B) ^ C == A ^ (B ^ C)
    • A ^ B == B ^ A
  • 所以:
    • 当:a = a ^ b
    • 则:b = a ^ b = a ^ b ^ b = a
    • 则:a = a ^ b ^ a = b
  • 注意,a和b不可以是同一块内存

0.1.2 用异或找数

题目:1)一个整数数组,只有一个数字出现了奇数次,其他数字都出现了偶数次,找出这个数

int find(vector<int>& arr){
  	int res = 0;
	for(int i = 0; i < arr.size(); i++) res ^= arr[i];
	return res;  
}

题目:2)一个整数数组,只有两个个数字出现了奇数次,其他数字都出现了偶数次,找出这个数

vector<int>& find(vector<int>& arr){
  	int res1 = 0;
	for(int i = 0; i < arr.size(); i++) res1 ^= arr[i];
	int m = res1 & (~res1 + 1);//提取二进制数最右侧的1,这一位俩个数不一样
    int res2 = 0;
    for(int i = 0; i < arr.size(); i++){
        if((arr[i] & m)==0) res2 ^= arr[i];//找到了其中一个数
    } 
    res1 = res1 ^ res2;//根据这一个数和两者的异或,得到另一个数
    return {res1,res2};  
}

1.排序算法

1.1 简单排序

1.1.1 选择排序

  • 时间O(N2) 、空间O(1)、不稳
void SelectionSort(vector<int>& data){
	for(int i = 0; i< data.size() - 1; i++){
		int min = i;
		for(int j = i + 1; j < data.size(); j++){
			if(data[j] < data[min]) min = j;
		}
		swap(data,i,min);
	}
}

1.1.2 冒泡排序

  • 时间O(N2) 、空间O(1)、稳
void BubbleSort(vector<int>& data){
	for(int i = data.size() - 1; i > 0; i--){
		for(int j = 0; j < i; j++){
			if(data[j] > data[j+1]) swap(data,j,j+1);
		}
	}
}

1.1.3 插入排序

  • 时间O(N2) 、空间O(1)、稳
  • 样本数少(<60)一般使用插入排序
void InsertionSort(vector<int>& data){
	for(int i = 1; i < data.size(); i++){
		for(int j = i; j > 0 && data[j] < data[j-1]; j--){
			swap(data,j,j-1);
		}
	}
}

1.2 特殊排序

1.2.1 归并排序

  • 时间O(NlogN)、空间O(N)、稳
  • 升级版本是TimSort
void MergeSort(vector<int>& data){
    Split(data,0,data.size()-1);
}

void Split(vector<int>& data, int leftPos, int rightPos){
    if(leftPos == rightPos) return;
    int midPos = 1 + leftPos + (rightPos - leftPos) / 2;
    Split(data,leftPos,midPos-1);
    Split(data,midPos,rightPos);
    Merge(data,leftPos,midPos,rightPos);
}

void Merge(vector<int>& data, int leftPtr, int rightPtr, int endPos){
    vector<int> res(endPos - leftPtr + 1);
    int i = leftPtr; int j = rightPtr; int k = 0;
    while(i <= rightPtr-1 && j <= endPos) res[k++] = data[i] <= data[j] ? data[i++] : data[j++];
    while(i <= rightPtr-1) res[k++] = data[i++];
    while(j <= endPos) res[k++] = data[j++];
    for(int m = 0; m < res.size(); m++) data[leftPtr+m] = res[m];
}

1.2.2 快速排序

  • 时间O(NlogN) 、空间O(logN)、不稳
  • 升级版是:双轴快排
void QuickSort(vector<int>& data){
    Split(data,0,data.size()-1);
}

void Split(vector<int>& data, int leftPos, int rightPos){
    if(leftPos >= rightPos) return;
    int midPos = Partition(data,leftPos,rightPos);
    Split(data,leftPos,midPos-1);
    Split(data,midPos+1,rightPos);
}

int Partition(vector<int>& data, int leftPos, int rightPos){
    int lptr = leftPos; int rptr = rightPos-1; int pivot = data[rightPos];
    while(lptr <= rptr){
        while(lptr <= rptr && data[lptr] <= pivot) lptr++;
        while(lptr <= rptr && data[rptr] > pivot) rptr--;
        if(lptr < rptr) swap(data,lptr,rptr);
    }
    swap(data,lptr,rightPos);
    return lptr;
}

1.2.3 希尔排序

  • 时间O(N1.3) 、空间O(1)、不稳
void ShellSort(vector<int>& data){
	for(int gap = data.size()/2; gap > 0; gap /= 2)
    {
        for(int i = gap; i < data.size(); i++)
        {
            for(int j = i; j > gap-1 && data[j] < data[j-gap]; j -= gap){
                swap(data,j,j-gap);}
        }
    }
}

1.3 桶排序

1.3.1 计数排序

时间O(N+K)、空间O(N+K)、稳

void CountingSort(vector<int>& data)
{
    int min = 0; int max = 0; 
    for(int i = 0; i < data.size(); i++){
        if(data[i] > max) max = data[i];
        if(data[i] < min) min = data[i];
    }
    vector<int> count(max - min);
    vector<int> res(data.size());
    for(int i = 0; i < data.size(); i++) count[data[i]-min]++;
    for(int i = 1; i < count.size(); i++) count[i] += count[i-1];
    for(int i = data.size()-1; i >= 0; i--) res[--count[data[i]-min]] = data[i];
    for(int i = 0; i < data.size(); i++) data[i] = res[i];
}

1.3.2 基数排序

时间O(N*K)、空间O(N)、稳定

void RadixSort(vector<int>& data){
    int max = 0,times = 0;
	for (int i = 0; i < data.size(); i++) if (data[i] > max) max = data[i];
	for (; max > 0; max /= 10) times++;
    
    for (int i = 0; i < times; i++)
	{
		vector<int> res(data.size());
		int count[10] = {};
		int dev = pow(10, i);
		for (int j = 0; j < data.size(); j++) count[data[j] / dev % 10]++;
		for (int j = 1; j < 10; j++) count[j] += count[j - 1];
		for (int j = data.size() - 1; j >= 0; j--){
            res[--count[data[j] / dev % 10]] = data[j];}
		for (int j = 0; j < data.size(); j++) data[j] = res[j];
	}
}

1.4 堆排序

  • 时间O(NlogN)、空间O(1)、不稳
void heapInsert(vector<int>& arr, int index)
{
	while (arr[index] > arr[(index - 1) / 2])
	{
		swap(arr, index, (index - 1) / 2);
		index = (index - 1) / 2;
	}
}

void heapify(vector<int>& arr, int index, int heapSize)
{
	int leftChild = 2 * index + 1;
	while (leftChild < heapSize) 
	{
		int large = leftChild + 1 < heapSize && arr[leftChild + 1] > arr[leftChild] ? leftChild + 1 : leftChild;
		large = arr[large] > arr[index] ? large : index;
		if (large == index) break;
		swap(arr, large, index);
		index = large;
		leftChild = 2 * index + 1;
	}
}

void HeapSort(vector<int>& arr)
{
	for (int i = 0; i < arr.size(); i++) heapInsert(arr, i);
	int heapSize = arr.size();
	swap(arr, 0, --heapSize);
	while (heapSize > 0)
	{
		heapify(arr, 0, heapSize);
		swap(arr, 0, --heapSize);
	}
}

1.5 题目

1.5.1 小和问题 – 改写归并排序

public static int smallSum(int[] arr) {
   
   
		if (arr == null || arr.length < 2) {
   
   
			return 0;
		}
		return mergeSort(arr, 0, arr.length - 1);
	}

public static int mergeSort(int[] arr, int l, int r) {
   
   
    if (l == r) {
   
   
        return 0;
    }
    int mid = l + ((r - l) >> 1);
    return mergeSort(arr, l, mid) 
        + mergeSort(arr, mid + 1, r) 
        + merge(arr, l, mid, r);
}

public static int merge(int[] arr, int l, int m, int r) {
   
   
    int[] help = new int[r - l + 1];
    int i = 0;
    int p1 = l;
    int p2 = m + 1;
    int res = 0;
    while (p1 <= m && p2 <= r) {
   
   
        res += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0;
        help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
    }
    while (p1 <= m) {
   
   
        help[i++] = arr[p1++];
    }
    while (p2 <= r) {
   
   
        help[i++] = arr[p2++];
    }
    for (i = 0; i < help.length; i++) {
   
   
        arr[l + i] = help[i];
    }
    return res;
}

1.5.2 对基本有序数组排序 – 改写堆排序

  • 每个数字都没有移动超过k步
public void sortedArrDistanceLessK(int[] arr, int k) {
   
   
    PriorityQueue<Integer> heap = new PriorityQueue<>();
    int index = 0;
    for (; index < Math.min(arr.length, k); index++) {
   
   
        heap.add(arr[index]);
    }
    int i = 0;
    for (; index < arr.length; i++, index++) {
   
   
        heap.add(arr[index]);
        arr[i] = heap.poll();
    }
    while (!heap.isEmpty()) {
   
   
        arr[i++] = heap.poll();
    }
}

1.5.3 荷兰国旗问题 – 快排前身

public static int[] partition(int[] arr, int l, int r, int p) {
   
   
    int less = l - 1;
    int more = r + 1;
    while (l < more) {
   
   
        if (arr[l] < p) {
   
   
            swap(arr, ++less, l++);
        } else if (arr[l] > p) {
   
   
            swap(arr, --more, l);
        } else {
   
   
            l++;
        }
    }
    return new int[] {
   
    less + 1, more - 1 };
}

1.5.4 比较器 – 自定义结构排序

public static class Student {
   
   
    public String name;
    public int id;
    public int age;

    public Student(String name, int id, int age) {
   
   
        this.name = name;
        this.id = id;
        this.age = age;
    }
}

public static class IdAscendingComparator implements Comparator<Student> {
   
   
    @Override
    public int compare(Student o1, Student o2) {
   
   
        return o1.id - o2.id;
    }
}

2. 链表

2.1 基本概念

  • 哈希表:
    • UnorderdMap – Key-Value
    • UnorderdSet – value
    • 主要方法:add、remove、put、get – 时间均为O(1)
  • 有序表:
    • OrderdMap – Key-Value
    • OrderdSet – value
    • 主要方法:put、get、firstkey、lastkey、floorkey、ceillingkey、remove – 时间为O(logN)
  • 基础类型按照值传递、自定义类型引用传递
HashSet<Integer> hashSet1 = new HashSet<>();
	hashSet1.add(3);
	hashSet1.remove(3);
	hashSet1.contains(3)
HashMap<String, Integer> hashMap1 = new HashMap<>();
	hashMap1.put(str1, 1);
	hashMap1.containsKey(str1);
	hashMap1.get(str1);
TreeMap<Integer, String> treeMap1 = new TreeMap<>();
	treeMap1.put(7, "我是7");
	treeMap1.containsKey(5);
	treeMap1.get(5);
	treeMap1.firstKey();
	treeMap1.lastKey();
	treeMap1.floorKey(8);
	treeMap1.ceilingKey(8);
	treeMap1.remove(5);
  • 有序表中存放自定义数据类型时,还需要提供比较器
public class Node{
   
   
    public int value;
    public Node next;
}//单链表

public class Node{
   
   
    public int value;
    public Node next;
    public Node last;
}//双链表
  • 基本的解题思路:
    • 笔试的时候,找时间复杂度最低的方法,借助额外的数据结构来解题
    • 面试的时候,还需要空间复杂度尽可能低,尽可能不借助额外的数据结构来解题
    • 技巧:使用辅助数据结构、使用快慢指针

2.2 快慢指针方法

快指针 F 和慢指针 S 都从头开始,但 F 一次走两步,S 一次走一步

当 F 到达null的时候,S 到达中点

2.3 题目

2.3.1 最基本的题目

  • 反转单链表、双链表
public static Node reverseList(Node head) {
   
   // 单链表反转
    Node pre = null;
    Node next = null;
    while (head != null) {
   
   
        next = head.next;
        head.next = pre;
        pre = head;
        head = next;
    }
    return pre;
}

public static DoubleNode reverseList(DoubleNode head) {
   
   // 双链表反转
    DoubleNode pre = null;
    DoubleNode next = null;
    while (head != null) {
   
   
        next = head.next;
        head.next = pre;
        head.last = next;
        pre = head;
        head = next;
    }
    return pre;
}
  • 打印链表
public static void printLinkedList(Node head) {
   
   // 单链表打印
    System.out.print("Linked List: ");
    while (head != null) {
   
   
        System.out.print(head.value + " ");
        head = head.next;
    }
    System.out.println();
}

public static void printDoubleLinkedList(DoubleNode head) {
   
   // 双链表打印
    System.out.print("Double Linked List: ");
    DoubleNode end = null;
    while (head != null) {
   
   // 正向打印
        System.out.print(head.value + " ");
        end = head;
        head = head.next;
    }
    System.out.print("| ");
    while (end != null) {
   
   // 反向打印
        System.out.print(end.value + " ");
        end = end.last;
    }
    System.out.println();
}
  • 打印两个有序链表的公共部分(归并的思路)
public static void printCommonPart(Node head1, Node head2) {
   
   
    System.out.print("Common Part: ");
    while (head1 != null && head2 != null) {
   
   
        if (head1.value < head2.value) {
   
   
            head1 = head1.next;
        } else if (head1.value > head2.value) {
   
   
            head2 = head2.next;
        } else {
   
   
            System.out.print(head1.value + " ");
            head1 = head1.next;
            head2 = head2.next;
        }
    }
    System.out.println();
}

2.3.2 回文链表

如1->2->2->1为回文列表

  • 方法一:

利用栈,将结点依次放入栈中,再挨个弹出与原列表逐个比较

public static boolean isPalindrome1(Node head) {
   
    // space O(N)
    Stack<Node> stack = new Stack<Node>();
    Node cur = head;
    while (cur != null) {
   
   
        stack.push(cur);
        cur = cur.next;
    }
    while (head != null) {
   
   
        if (head.value != stack.pop().value) {
   
   
            return false;
        }
        head = head.next;
    }
    return true;
}

public static boolean isPalindrome2(Node head) {
   
   // space O(N/2)
    if (head == null || head.next == null) {
   
   
        return true;
    }
    Node right = head.next;
    Node cur = head;
    while (cur.next != null && cur.next.next != null) {
   
   
        right = right.next;//慢指针
        cur = cur.next.next;//快指针
    }
    Stack<Node> stack = new Stack<Node>();
    while (right != null) {
   
   //从中点开始压栈
        stack.push(right);
        right = right.next;
    }
    while (!stack.isEmpty()) {
   
   //从栈顶一个一个弹出和链表从头部开始依次比较
        if (head.value != stack.pop().value) {
   
   
            return false;
        }
        head = head.next;//遍历链表
    }
    return true;
}
  • 方法二:

利用快慢指针找到中点,将后半截反序,两个指针从两边遍历到中点,逐个比较

public static boolean isPalindrome3(Node head) {
   
   // space O(1)
    if (head == null || head.next == null) {
   
   
        return true;
    }
    Node n1 = head;
    Node n2 = head;
    while (n2.next != null && n2.next.next != null) {
   
    // find mid node
        n1 = n1.next; // n1 -> mid
        n2 = n2.next.next; // n2 -> end
    }
    n2 = n1.next; // n2 -> right part first node
    n1.next = null; // mid.next -> null
    Node n3 = null;
    while (n2 != null) {
   
    // right part convert
        n3 = n2.next; // n3 -> save next node
        n2.next = n1; // next of right node convert
        n1 = n2; // n1 move
        n2 = n3; // n2 move
    }
    n3 = n1; // n3 -> save last node
    n2 = head;// n2 -> left first node
    boolean res = true;
    while (n1 != null && n2 != null) {
   
    // check palindrome
        if (n1.value != n2.value) {
   
   
            res = false;
            break;
        }
        n1 = n1.next; // left to mid
        n2 = n2.next; // right to mid
    }
    n1 = n3.next;
    n3.next = null;
    while (n1 != null) {
   
    // recover list
        n2 = n1.next;
        n1.next = n3;
        n3 = n1;
        n1 = n2;
    }
    return res;
}

2.3.3 单链表荷兰国旗问题

题目:给定一个单链表,一个Pivot,将链表重新排列为:小于Pivot、等于Pivot、大于Pivot三个区域

  • 方法一:

建立一个结点数组,用Partition的方法来对数组里的节点重新排列,最后连接起来

public static Node listPartition1(Node head, int pivot) {
   
   
    if (head == null) {
   
   
        return head;
    }
    Node cur = head;
    int i = 0;
    while (cur != null) {
   
   // 计算结点个数
        i++;
        cur = cur.next;
    }
    Node[] nodeArr = new Node[i];// 用结点个数来建立新数组
    i = 0;
    cur = head;
    for (i = 0; i != nodeArr.length; i++) {
   
   // 将结点依次填入数组中
        nodeArr[i] = cur;
        cur = cur.next;
    }
    arrPartition(nodeArr, pivot);// 对数组进行荷兰国旗问题处理
    for (i = 1; i != nodeArr.length; i++) {
   
   // 对排列好的结点依次连接成链表
        nodeArr[i - 1].next = nodeArr[i];
    }
    nodeArr[i - 1].next = null;
    return nodeArr[0];
}

public static void arrPartition(Node[] nodeArr, int pivot) {
   
   // 对数组进行荷兰国旗问题处理
    int small = -1;
    int big = nodeArr.length;
    int index = 0;
    while (index != big) {
   
   
        if (nodeArr[index].value < pivot) {
   
   
            swap(nodeArr, ++small, index++);
        } else if (nodeArr[index].value == pivot) {
   
   
            index++;
        } else {
   
   
            swap(nodeArr, --big, index);
        }
    }
}

  • 方法二:

使用六个指针:SH、ST、EH、ET、BH、BT 分别指向 小于区域、等于区域、大于区域的头和尾

​ 一开始六个指针都指向null,

​ 发现第一个满足区域条件时,该区域的H指针指向它就不动了

​ 每发现一个满足该区域条件时,该区域的T指针指向它,该区域的各个节点相连

​ 最后:ST->EH、ET->BH,而SH为新链表的头

要注意边界,因为某个区域可能为空

public static Node listPartition2(Node head, int pivot) {
   
   
		Node sH = null; // small head
		Node sT = null; // small tail
		Node eH = null; // equal head
		Node eT = null; // equal tail
		Node bH = null; // big head
		Node bT = null; // big tail
		Node next = null; // save next node
		// every node distributed to three lists
		while (head != null) {
   
   
			next = head.next;
			head.next = null; // 断开连接
			// H如果为空,H就要等于head,
            // 否则让尾端的下一个结点连head,
            // 不管怎么样,在最后都要让T等于head
            if (head.value < pivot) {
   
    // 小于区域
				if (sH == null) sH = head;
				else sT.next = head;	
				sT = head;
			}
			else if (head.value == pivot) {
   
    // 等于区域
				if (eH == null) eH = head;
				else eT.next = head;
				eT = head;
			}
			else {
   
    // 大于区域
				if (bH == null) bH = head;
				else bT.next = head;
				bT = head;
			}
			head = next;
		}
		// small and equal reconnect
		if (sT != null) {
   
   
			sT.next = eH;
			eT = eT == null ? sT : eT;
		}
		// all reconnect
		if (eT != null) {
   
   
			eT.next = bH;
		}
		return sH != null ? sH : eH != null ? eH : bH;
	}

2.3.4 复制特殊链表

题目:一种新的链表,除了有Node next成员外,还有一个Node rand随机指向一个节点,将该链表复制一遍

  • 方法一:

用Map:key-value的查找方法,挨个对应

public static Node copyListWithRand1(Node head) {
   
   
    HashMap<Node, Node> map = new HashMap<Node, Node>();
    Node cur = head;
    while (cur != null) {
   
    // 把每个结点和结点的复制品放入哈希表
        map.put(cur, new Node(cur.value));
        cur = cur.next;
    }
    cur = head;
    while (cur != null) {
   
    // 遍历链表,每个链表的复制品按照对应关系连接
        map.get(cur).next = map.get(cur.next);
        map.get(cur).rand = map.get(cur.rand);
        cur = cur.next;
    }
    return map.get(head);
}
  • 方法二:

在每个节点后面复制一个该节点,然后遍历,一一对应rand指针

然后逐个解开next,对应新next

public static Node copyListWithRand2(Node head) {
   
   
    if (head == null) {
   
   
        return null;
    }
    Node cur = head;
    Node next = null;
    // copy node and link to every node 在每个节点后插入一个自己的复制品
    while (cur != null) {
   
   
        next = cur.next;
        cur.next = new Node(cur.value);
        cur.next.next = next;
        cur = next;
    }
    cur = head;
    Node curCopy = null;
    // set copy node rand 复制品的rand挨个连接对应复制品 cur.rand.next
    while (cur != null) {
   
    
        next = cur.next.next;
        curCopy = cur.next;
        curCopy.rand = cur.rand != null ? cur.rand.next : null;
        cur = next;
    }
    Node res = head.next;
    cur = head;
    // split 复制品和原链表分离的同时,将复制品的next连对 
    while (cur != null) {
   
   
        next = cur.next.next;
        curCopy = cur.next;
        cur.next = next;
        curCopy.next = next != null ? next.next : null;
        cur = next;
    }
    return res;
}

*2.3.5 一个单链表是否有环

a
b
c
d
e
#
A
B
C
D
E

上面为无环,下面为有环,B为入环节点

  • 方法一:

用set来依次放入节点,当第一次查找能找到时,这个节点就是入环节点

  • *方法二:改进的快慢指针

S 和 F 从头节点出发,若 F 到了null,则一定无环

若 S 和 F 相遇,则一定有环

此时,让 F 回到起点,S 和 F 一次走一步,它们一定会在入环节点相遇

public static Node getLoopNode(Node head) {
   
   
    if (head == null || head.next == null || head.next.next == null) {
   
   
        return null; // 快慢指针起点若为空,则直接判断无环
    }
    Node n1 = head.next; // n1 -> slow
    Node n2 = head.next.next; // n2 -> fast
    while (n1 != n2) {
   
    // 快慢指针相遇
        if (n2.next == null || n2.next.next == null) {
   
   
            return null; // 发现终点说明无环
        }
        n2 = n2.next.next;
        n1 = n1.next;
    }
    n2 = head; // n2 -> walk again from head 快指针回到链表头
    while (n1 != n2) {
   
   // 各自都一次一步直到相遇
        n1 = n1.next;
        n2 = n2.next;
    }
    return n1;// 返回相遇的点
}

2.3.6 两个单链表相交

题目:两个单链表head1和head2,可能有环,判断它们相交与否,若相交,返回第一个相交结点

先判断是不是有环:

  1. 都无环,有两种情况
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值