算法基础班

2022.11.26认识复杂度和排序算法

P3.复杂度和简单排序算法

2022.11.26

时间复杂度 空间复杂度

选择排序

  1. 时间复杂度O(n^2),空间复杂度O(1)

  2. 寻找[i, n)区间里的最小值,并放在i位置上,i++

  3. for (int i = 0; i < n; i++) {
            // 寻找[i, n)区间里的最小值,并放在i位置上
            int minIndex = i;
            for (int j = i + 1; j < n; j++)
                if (arr[j] < arr[minIndex])
                    minIndex = j;
        	swap(arr[i], arr[minIndex]);
        }
    

冒泡排序

  1. 时间复杂度O(n^2),空间复杂度O(1)

  2. 每次比较相邻两个数的的大小,将大的放到右边,这样每一轮都将找到待排序数据中的最大值,并放到排好序的位置

  3. void bubblesort(int *a,int len)	//形参a取到实参a传递过来的数组首地址
                                    //然后解引用,取到数组的值 
    {
    	for (int i=0;i<len-1;i++)	//i控制排序的轮数 
    	{
    		for (int j=0;j<len-i-1;j++)	//j控制每轮需要比较的次数 
    		{
    			if(a[j+1]<a[j])	//不满足正序要求,交换顺序 
    			{
    				int temp=a[j];
    				a[j]=a[j+1];
    				a[j+1]=temp;
    			}
    		}
    	}	
    

插入排序

  1. 时间复杂度O(n^2),空间复杂度O(1)
  2. 从i = 1开始,比较与前一个,若比前一个小则交换,知道不能换为止,但算法复杂度按照算法最差情况估计
void insertionSort(int &arr){
	if(arr == null || arr.size() < 2) return arr;
    for(int i = 1; i < arr.size(); i++){ // i从1开始比较
		for (int j = i - 1; j >= 0 && arr[j] > arr[j+1]; j--){
        	swap(arr[j], arr[j+1]);
        }
    }
}

异或运算拓展

  • 法则:相同为0,不同为1
  • 还可以理解为无进位相加
    1. 0 ^ N = N; N ^ N = 0
    2. 满足交换律和结合律(利用无进制相加来解释:看某位的值看该为1的个数,与顺序无关)
    3. 一批数的异或结果唯一
  • 不用额外变量交换两个值
swap(int a, int b){    //利用异或交换a,b的值,即
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
}

上述代码有效条件:a和b值可以一样,但a和b地址不同!不然就为0

异或面试题

题目:int [ ], 1)一种数出现奇数次,其余均为偶数次; 2)两种数出现奇数次,其余均为偶数次

找到奇数次数字,时间复杂度O(n), 空间复杂度O(1)

1)int eor = 0; 令eor异或数组内所有值,则最终eor = a;即为奇数次数字

2)int eor = 0; 令eor异或数组内所有值,则最终eor = a^b != 0; 则eor在某位上一定不为0,表示a和b在该位上不同;eor’ = 0 去异或该位上不为0的数,则可得到a或b之一,则另一数为eor ^ eor’

int eor = 0;
for (int i = 0; i < arr.size(); i++){
    eor ^= arr[i];
}
// eor = a ^ b;
// eor != 0;
// eor 中必有一位为1
int rightOne = eor & (~ eor + 1); //***提取eor中最右侧的1!!!***
int onlyOne = 0;
for (int cur : arr){
    if ((cur & rightOne)= 0){
        onlyOne ^= cur; // 异或该位上为1的数
	} //onlyOne 为a和b right One位上位1的数字
}
cout << onlyOne << " " << (eor ^ onlyOne);

二分法详解与扩展

在有序数组中找某个数是否存在

  • 时间复杂度为O(log2N)->O(logN)

在有序数组中,找>=某个数最左侧的的位置

  • 也可以二分,但需要记录

局部最小值问题

  • 若0位置和N-1位置不是局部最小,则0至N-1处必存在局部最小(类似费马定理)
  • 二分_局部最小

对数器的概念和使用(验证OG)

对数器
//产生非确定性随机数(多次次运行时每次产生的随机数不一样)
#include <iostream>
#include <random>

int main()
{
    std::random_device e; 
    std::uniform_real_distribution<double> u(0, 1); //随机数分布对象 
    for (size_t i = 0; i < 10; ++i)  //生成范围为0-1的随机浮点数序列 
       std::cout << u(e) << " ";
    std::cout << std::endl;
    
    return 0;
}

#include "stdafx.h"
#include "iostream"
#include "ctime"
#include "cstdlib"
using namespace std;
#define N  999 //精度为小数点后面3位
int main()
{
	float num;
	int i;
	float random[10];
	srand(time(NULL));//设置随机数种子,使每次产生的随机序列不同
	for (int i = 0; i < 10; i++)
	{
		random[i] = rand() % (N + 1) / (float)(N + 1);
	}
	for (int i = 0; i < 10; i++)
	{
		cout << random[i] << endl; //输出产生的10个随机数
	}
    return 0;
}

P4.认识O(NlogN)的排序

2022.11.26 27

递归行为和递归行为复杂度

引子:求mid mid = L + (R - L) / 2 == L + (R - L) >> 1 //右移一位相当于除二,更快

​ 递归逻辑图:利用压栈、出栈、树

master公式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DotlXk5d-1671605563885)(C:\Users\MSI-NB\Desktop\左程云\图片\master.PNG)]

归并排序

时间复杂度

  • O(N*logN) //T(N) = 2 * T(N / 2) + O(N) //相对于O(N^2) 排序并没有浪费比较行为,比较行为变成有序东西接下来传递

分离函数

void mergesort(int x,int y)			//分离,x 和 y 分别代表要分离数列的开头和结尾
{
	if (x==y) return;        //如果开头 = 结尾,那么就说明数列分完了,就要返回
	int mid=X + (Y - X) >> 1;            //将中间数求出来,用中间数把数列分成两段
	mergesort(x,mid);
	mergesort(mid+1,y);        //递归,继续分离
	merge(x,mid,y);        //分离玩之后就合并
}

合并函数

void merge(int low,int mid,int high) //归并
//low 和 mid 分别是要合并的第一个数列的开头和结尾,mid+1 和 high 分别是第二个数列的开头和结尾
{
	int i=low,j=mid+1,k=low;
    //i、j 分别标记第一和第二个数列的当前位置,k 是标记当前要放到整体的哪一个位置
	while (i<=mid && j<=high)    //如果两个数列的数都没放完,循环
	{
		if (a[i]<a[j])
			b[k++]=a[i++];
		else
			b[k++]=a[j++];   //将a[i] 和 a[j] 中小的那个放入 b[k],然后将相应的标记变量增加
	}        // b[k++]=a[i++] 和 b[k++]=a[j++] 是先赋值,再增加
	while (i<=mid)
		b[k++]=a[i++];
	while (j<=high)
		b[k++]=a[j++];    //当有一个数列放完了,就将另一个数列剩下的数按顺序放好
	for (int i=low;i<=high;i++)
		a[i]=b[i];                //将 b 数组里的东西放入 a 数组,因为 b 数组还可能要继续使用
}
void mergeSort(vector<int>& arr, int l, int h) {
    if (l == h) {
        return;
    }
    int mid = l + ((h - l) >> 1) ;
    mergeSort(arr, l, mid);
    mergeSort(arr, mid + 1, h);
    merge(arr, l, mid, h);
}

void merge(vector<int>& arr, int l, int mid, int h) {
    vector<int> help(h - l + 1);
    int low = l;
    int high = mid + 1;
    int index = 0;
    while (low <= mid && high <= h) {
        help[index++] = arr[low] < arr[high] ? arr[low++] : arr[high++];
    }
    while (low <= mid) {
        help[index++] = arr[low++];
    }
    while (high <= h) {
        help[index++] = arr[high++];
    }
    for (int i = 0; i < help.size(); i++) {
        arr[l + i] = help[i];
    }

}

归并排序拓展题目

小和问题

在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组 的小和。

例子:[1,3,4,2,5] 1左边比1小的数,没有; 3左边比3小的数,1; 4左边比4小的数,1、3; 2左边比2小的数,1; 5左边比5小的数,1、3、4、2; 所以小和为1+1+3+1+1+3+4+2=16

  • 思路:利用归并排序。问题可以转换为查找一个数右边比他大的数出现的次数,则先二分数组,利用并规排序,改变merge函数。
#include <iostream>
#include <vector>
using namespace std;
class Solution{
private:
    int merge(vector<int>&arr, int L, int mid, int R) {
    int p1 = L;
    int p2 = mid + 1;
    vector<int> help(R - L + 1);
    int res = 0;
    int i = 0;                      //i为help数组下标
    while (p1 <= mid && p2 <= R) {
        res += arr[p1] < arr[p2] ? arr[p1] * (R - p2 + 1) : 0;
        help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
    }
    while (p1 <= mid) {
        help[i++] = arr[p1++];
    }
    while (p2 <= R) {
        help[i++] = arr[p2++];
    }
    for (int i = 0; i < help.size(); i++) {
        arr[L + i] = help[i];
    }
    return res;
    }

    int mergeSort(vector<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:
    int smallSum(vector<int>&arr) {
    if (arr.size() < 2) {
        return 0;
    }
    return mergeSort(arr, 0, arr.size()-1);
    }

};
int main()
{
    vector<int> a = {1,3,4,2,5};
    class Solution s;
    cout << s.smallSum(a);
}

逆序对

在一个数组中,左边的数如果比右边的数大,则这两个数构成一个逆序对,请打印所有逆序 对

小和问题是右边的数比左边的数大,逆序对是左边的数比右边的数大,本质是一个问题。

为了保证merge时不比较单块数组内,因此数组内要降序排列,先放p2的

#include <iostream>
#include <vector>
using namespace std;
class Solution {
private:
    void merge(vector<int>& nums, int l, int mid, int r) {
        int p1 = l;
        int p2 = mid + 1;
        vector<int> help(r - l + 1);
        int i = 0;
        while (p1 <= mid && p2 <= r) {
            if (nums[p1] > nums[p2]) {
                int p3 = p2;
                while (p3 <= r) {
                    cout << nums[p1] << " " << nums[p3++] << endl;
                }
            }
            help[i++] = nums[p1] > nums[p2] ? nums[p1++] : nums[p2++];
        }
        while (p1 <= mid) {
            help[i++] = nums[p1++];
        }
        while (p2 <= r) {
            help[i++] = nums[p2++];
        }
        for (int i = 0; i < help.size(); i++) {
            nums[l + i] = help[i];
        }
    }
    void mergeSort(vector<int>& nums, int l, int r) {
        if (l == r) {
            return;
        }
        int mid = l + ((r - l) >> 1);
        mergeSort(nums, l, mid);
        mergeSort(nums, mid + 1, r);
        merge(nums, l, mid, r);
    }
public:
    void inversion(vector<int>& nums) {
        if (nums.size() < 2) {
            return;
        }
        else {
            mergeSort(nums, 0, nums.size() - 1);
        }
    }
};
int main()
{
    vector<int> a = { 5,4,3,2,1,0};
    class Solution s;
    s.inversion(a);
}

快排问题

荷兰国旗问题

问题一

给定一个数组arr,和一个数num,请把小于等于num的数放在数 组的左边,大于num的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度O(N)

利用双指针

#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
    vector<int> flag(vector<int>& nums, int val) {
        int slow = -1;
        int fast = 0;
        for (int fast = 0; fast < nums.size(); fast++) {
            if (nums[fast] <= val) {
                swap(nums[fast], nums[++slow]);
            }
        }
        return nums;
    }
};
int main()
{
    Solution solution;
    vector<int> a = { 1, 4, 5, 7, 4, 5,8, 1, 9, 6, 0, 5 };
    int val = 5;
    vector<int> b;
    b = solution.flag(a, val);
    for (auto i = b.begin(); i < b.end(); i++) {
        cout << *i << "";
    }
}
问题二

给定一个数组arr,和一个数num,请把小于num的数放在数组的 左边,等于num的数放在数组的中间,大于num的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度O(N)

#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
    vector<int> Netherlands_flag2(vector<int>& nums, int val) {
        vector<int> a;
        int left = -1;
        int i = 0;
        int right = nums.size();
        if (nums.size() < 1) {
            return a;
        }
        for (int fast = 0; fast < nums.size(); fast++) {
            if (fast == right) {
                return nums;
            }
            if (nums[fast] < val) {
                swap(nums[++left], nums[fast]);
            }
            else if (nums[fast] > val) {
                swap(nums[--right], nums[fast]);
                fast--;
            }
        }
    }
};
int main()
{
    vector<int> a = { 1,4,8,4,5,6,5,9,2,1,0 };
    int val = 5;
    Solution solution;
    vector<int> b = solution.Netherlands_flag2(a, val);
    for (auto iter = b.begin(); iter < b.end(); iter++) {
        cout << *iter;
    }
}

不改进的快排

  • 把数组范围中的最后一个数作为划分值,然后把数组通过荷兰国旗问题分成三个部分:

    左侧<划分值、中间==划分值、右侧>划分值

  • 对左侧范围和右侧范围,递归执行

分析:

  • 划分值越靠近两侧,复杂度越高;划分值越靠近中间,复杂度越低,复杂度O(n^2)

改进的快排(随机快速排序)

  • 在数组范围中,等概率随机选一个数作为划分值,然后把数组通过荷兰国旗问题分成三个部分:

    左侧<划分值、中间==划分值、右侧>划分值

  • 对左侧范围和右侧范围,递归执行

  • *时间复杂度为O(N*logN),空间复杂度O(logN)

P5.详解桶排序以及排序内容大总结

2022.11.27 28

堆概念

  1. 堆结构就是用数组实现的完全二叉树结构
  2. 完全二叉树中如果每棵子树的最大值都在顶部就是大根堆
  3. 完全二叉树中如果每棵子树的最小值都在顶部就是小根堆

堆结构操作函数

堆结构的heapInsert与heapify操作

  • index, (index-1)/2父, 2index左孩子, 2index+1右孩子
void heapInsert(int& arr, int index) { //向上插入O(logN)
		while (arr[index] > arr[(index - 1) / 2]) {
			swap(arr, index, (index - 1) /2);
			index = (index - 1)/2 ;
		}
	}
void heapify(int& arr, int index, int size) { //向下插入O(logN)
		int left = index * 2 + 1; //左孩子
		while (left < size) {
			int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left; //当右孩子存在,比较左右孩子谁大,取大的为largest
			largest = arr[largest] > arr[index] ? largest : index;
			if (largest == index) {
				break;
			}
			swap(arr, largest, index);
			index = largest;
			left = index * 2 + 1;
		}
	}

堆排序

  1. 先让整个数组都变成大根堆结构,建立堆的过程:

    1. 从上到下的方法,时间复杂度为O(N*logN)
    2. 从下到上的方法,时间复杂度为O(N)
  2. 把堆的最大值堆末尾的值交换,然后减少堆的大小之后,再去调整堆,一直周而复始,时间复杂度为O(N*logN),空间复杂度O(1)

  3. 堆的大小减小成0之后,排序完成

void heapSort(int& arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		for (int i = 0; i < arr.length; i++) {//将数组变为大根堆
			heapInsert(arr, i);
		}
		int size = arr.length;
		swap(arr, 0, --size); //0位和堆最后一个交换
		while (size > 0) {
			heapify(arr, 0, size);
			swap(arr, 0, --size);// size--相当于断联
		}
	}

堆排序题目

  • 已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法针对这个数据进行排序。
  • 思路:若k=6,则在0-6上进行小根堆排序,则第一个为最小值,接着往后移一位再取出次小值

堆排序可以使用c++内置优先级队列,但相当于黑盒,无法像自定义堆排序任意改变堆中元素的值而是需要进行重新扫描

class Solution {
public:
    void stackSort(vector<int>& nums, int val) {
        std::priority_queue<int, deque<int>, greater<int>> value;
        int index = 0;
        for (index = 0; index <= val; index++) { //前k个进入
            value.push(nums[index]);
        }
        int i = 0;
        for (i = 0; index <= value.size(); index++, i++) {
            nums[i] = value.top();
            value.pop();
            value.push(nums[index]);
        }
        while (!value.empty()) {
            nums[i++] = value.top();
            value.pop();
        }
    }
};

比较器

  • 即 重载运算符

  • c++教程

  • 返回正数,第二个放前面;返回负数,第一个放前面;返回0,无所谓

桶排序思想下的排序

计数排序

  • 类似Hash[]数组

基数排序

  • 按照最长数的位数,补齐每个数
  • 准备桶,首先按照个位进对应的桶,再从各位小的桶依次倒出数据(相当于按照个位排序)
  • 再按照十位数字进桶···(相当于按照十位数字排序)
int maxbits(vector<int> arr) { // 确定arr数组中最大位数
    int maxcount = INT32_MIN;
    for (int i = 0; i < arr.size(); i++) {
        int count = 0;
        int temp = arr[i];
        while (temp) {
            count++;
            temp = temp / 10;
        }
        maxcount = max(maxcount, count);
    }
    return maxcount;
}
void radixSort(vector<int>& arr, int maxbits) { 
    int size = arr.size();
    int j = 0;
    if (size < 2) {
        return;
    }
    vector<int> help(size);
    for (int d = 1; d <= maxbits; d++) { // maxbits相当于出桶入桶的次数
        vector<int> count(10);// 前缀表数组,10进制数字
        // 将第d位的数字放入前缀表数组中
        for (int i = 0; i < size; i++) {
            j = getDigit(arr[i], d);
            count[j]++;
        }
        // 将count数组变成真正的前缀表
        for (int i = 1; i < 10; i++) {
            count[i] = count[i] + count[i - 1];
        }
        // 利用前缀表出桶,要从右往左遍历
        for (int i = size - 1; i >= 0; i--) {
            int place = count[getDigit(arr[i], d)];
            help[place - 1] = arr[i];
            count[getDigit(arr[i], d)]--;
        }
        // 改变arr值s
        for (int i = 0; i < size; i++) {
            arr[i] = help[i];
        }
    }
}
int getDigit(int number, int digit) {// 得到arr[i]的第digit位的值
    return ((number / (int)(pow(10, digit - 1))) % 10);
}
  • 时间复杂度

    最好O(N)、最坏O(N^2)、平均O(N*K)

  • 空间复杂度

    O(N*K)

排序总结

稳定性

同样值的个体之间,如果不因为排序而改变相对次序,就是这个排序是有稳定性的;否则就没有

排序总结

基于比较的排序,时间复杂度没有小于O(N*logN)

没有时间复杂度为O(N*logN),空间复杂度O(N)的排序

工程上改进

  • 充分利用O(N*logN)和O(N^2)排序各自的优势

    例如先利用快排进行partition,当数据量较小时,采用插入排序

  • 稳定性的考虑

    例如自带的sort排序,会根据输入数据不同选择归并或者快排

P6.链表

2022.11.28 29

哈希表

  • 时间复杂度认为O(1)
  • 以基础数据类型,则全部拷贝一份作为key;若以自己定义的数据类型,则记录内存地址为key

有序表

  • 放入有序表的东西,如果是基础类型,内部按值传递,内存占用就是这个东西的大小
  • 放入有序表的东西,如果不是基础类型,必须提供比较器,内部按引用传递,内存占用是这个东西内存地址的大小

链表

翻转单双项列表

利用双指针

方法论

  • 对于笔试,不用太在乎空间复杂度,一切为了时间复杂度
  • 对于面试,时间复杂度依然放在第一位,但是一定要找到空间最省的方法
  • 经常会利用哈希表等额外数据结构或者快慢指针

题目1 判断一个链表是否为回文结构

给定一个单链表的头节点head,请判断该链表是否为回文结构

笔试时,利用stack,不考虑空间复杂度。

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        std::stack<int> st;
        ListNode* cur = head;
        ListNode* cur1 = head;
        while(cur) {
            st.push(cur->val);
            cur = cur->next;
        }
        while(cur1) {
            if (cur1->val == st.top()) {
                st.pop();
            }else {
                return false;
            }
            cur1 = cur1->next;
        }
        return true;
    }
};
快慢指针找中点
  • 找中点策略:快慢指针

    快指针一次走两步,慢指针一次走一步,快指针到头则慢指针到中点。

面试时候,时间复杂度达到O(N),额外空间复杂度达到O(1)

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        ListNode* cur = head;
        ListNode* pre = head;
        ListNode* first = head;
        ListNode* last = nullptr;
        while (cur->next != nullptr && cur->next->next != nullptr) {
            cur = cur->next->next;
            pre = pre->next;
        }
        if (cur->next == nullptr) {
            first = head;
            last = reverse(pre);
            while (first->next != nullptr && last->next !=nullptr) {
                if (first->val != last->val) return false;
                else {
                    first = first->next;
                    last = last->next;
                }
            }
            return true;
        }else {
            first = head;
            last = reverse(pre->next);
            pre->next = nullptr;
            while (first->next != nullptr && last->next !=nullptr) {
                if (first->val != last->val) return false;
                else {
                    first = first->next;
                    last = last->next;
                }
            }
            if (first->val != last->val) return false;
            return true;
        }
    }
    ListNode* reverse(ListNode* head) {
        ListNode* cur = head;
        ListNode* temp = nullptr;
        ListNode* pre = nullptr;
        while (cur != nullptr) {
            temp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }
};

题目2将单向链表按某值划分成左边小、中间相等、右边大的形式

题目:给定一个单链表的头节点head,节点的值类型是整型,再给定一个整数pivot。实现一个调整链表的函数,将链表调整为左部分都是值小于pivot的节点,中间部分都是值等于pivot的节点,右部分都是值大于pivot的节点

思路:创建六个变量,小等大的头和尾,不断更新直至结束,接着串起来。

  • 笔试做法,利用额外数组空间,在数组内进行快排,最后再放入链表中时间复杂度O(N logN),空间复杂度O(N)
void listPartition(ListNode* head, int val) {
    if (head == nullptr) {
        return;
    }
    vector<int> arr;
    ListNode* cur = head;
    while (cur) {
        arr.push_back(cur->val);
        cur = cur->next;
    }
    quickSort(arr, val);
    cur = head;
    int i = 0;
    while (cur) {
        cur->val = arr[i++];
        cur = cur->next;
    }
}
  • 面试做法

申请六个变量:小于给定值的头、尾,等于给定值的头、尾,大于给定值的头、尾;然后遍历一遍链表,将各个节点插入到相应的区域;最后将小于区域、等于区域和大于区域首尾依次相连。注意:三个区域均有可能为空,在相连的时候一定要讨论清楚边界!

equebigsmall
ListNode* listPartition(ListNode* head, int val) {
    ListNode* SH = nullptr;
    ListNode* ST = nullptr;
    ListNode* EH = nullptr;
    ListNode* ET = nullptr;
    ListNode* BH = nullptr;
    ListNode* BT = nullptr;
    ListNode* cur = head;
    while (cur) {
        // 将链表分配给六个指针
        ListNode* temp = cur->next;
        if (cur->val < val) {
            cur->next = nullptr;
            if (SH == nullptr) {
                SH = cur;
                ST = cur;
            }
            else {
                ST->next = cur;
                ST = cur;
            }
        }
        else if (cur->val == val) {
            cur->next = nullptr;
            if (EH == nullptr) {
                EH = cur;
                ET = cur;
            }
            else {
                ET->next = cur;
                ET = cur;
            }
        }
        else if (cur->val > val) {
            cur->next = nullptr;
            if (BH == nullptr) {
                BH = cur;
                BT = cur;
            }
            else {
                BT->next = cur;
                BT = cur;
            }
        }
        cur = temp;
    }
        // 考虑多种情况,将六个指针串起来
        if (SH == nullptr) {
            if (EH == nullptr) {// 没小于没等于只有大于
                return BH;
            }
            else { 
                if (BH == nullptr) {
                    return EH;// 没小于有等于没大于
                }
                else {
                    ET->next = BH;
                    return EH;// 没小于有等于有大于
                }
            }
        }
        else {
            if (EH == nullptr) {
                if (BH == nullptr) {// 有小于没等于没大于
                    return SH;
                }
                else {// 有小于没等于有大于
                    ST->next = BH;
                    return SH;
                }
            }
            else {
                if (BH == nullptr) {// 有小于有等于没大于
                    ST->next = EH;
                    return SH;
                }
                else {// 有小于有等于有大于
                    ST->next = EH;
                    ET->next = BH;
                    return SH;
                }
            }
        }

}

题目3两个单链表相交的一系列问题

给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的 第一个节点。如果不相交,返回null。

思路:可以用哈希表找环;或者用快慢指针找环

  • 快慢指针找环入口:快指针走两步,慢指针走两步,如果有环,则必定在环内相遇,记录相遇点,快指针回到起始位置,与慢指针同时开始一次一步,则必在入口处相遇
ListNode* haveLoop1(ListNode* head) {// 利用哈希表
    ListNode* cur = head;
    unordered_set<ListNode*> uset;
    while (cur) {
        if (uset.find(cur) == uset.end()) {
            uset.insert(cur);
            cur = cur->next;
        }
        else {
            return cur;  // 有环
        }
    }
    return nullptr; // 没有环
}


ListNode* haveLoop2(ListNode* head) {// 利用快慢指针找入环点,快慢指针在环里相遇,快指针来到开头,与慢指针一次一步,再相遇时为入口
    if (head == nullptr || head->next->next == nullptr) {
        return nullptr;
    }
    ListNode* fast = head->next->next;
    ListNode* slow = head->next;
    while (fast != slow) {
        if (fast->next == nullptr || fast->next->next == nullptr) {
            return nullptr;
        }
        fast = fast->next->next;
        slow = slow->next;
    }
    fast = head;
    while (fast != slow) {
        slow = slow->next;
        fast = fast->next;
    }
    return fast;
}


ListNode* noLoop(ListNode* head1, ListNode* head2) {
    if (head1 == nullptr || head2 == nullptr) {
        return nullptr;
    }
    int n = 0;
    ListNode* cur = head1;
    while (cur->next != nullptr) {
        n++;
        cur = cur->next;
    }
    cur = head2;
    while (cur->next != nullptr) {
        n--;
        cur = cur->next;
    }
    ListNode* cur1 = n > 0 ? head1 : head2;   // cur1为长链表
    ListNode* cur2 = n > 0 ? head2 : head1;   // cur2为短链表
    n = abs(n);
    while (n != 0) {
        cur1 = cur1->next;
        n--;
    }
    while (cur1 != nullptr && cur2 != nullptr) {
        cur1 = cur1->next;
        cur2 = cur2->next;
        if (cur1 == cur2) {
            return cur1;
        }
    }
    return nullptr;
}



ListNode* bothLoop(ListNode* head1, ListNode* loop1, ListNode* head2, ListNode* loop2) {
    if (loop1 == loop2) {
        int n = 0;
        ListNode* cur = head1;
        while (cur->next != loop1) {
            n++;
            cur = cur->next;
        }
        cur = head2;
        while (cur->next != loop1) {
            n--;
            cur = cur->next;
        }
        ListNode* cur1 = n > 0 ? head1 : head2;   // cur1为长链表
        ListNode* cur2 = n > 0 ? head2 : head1;   // cur2为短链表
        n = abs(n);
        while (n != 0) {
            cur1 = cur1->next;
            n--;
        }
        while (cur1 != cur2) {
            cur1 = cur1->next;
            cur2 = cur2->next;
        }
        return cur1;
    }
    else {
        ListNode* cur1 = head1->next;
        while (cur1 != loop2) {
            if (cur1 == loop1) {
                return nullptr;
            }
            cur1 = cur1->next;
        }
        return loop2;
    }
}



ListNode* getIntersectNode(ListNode* head1, ListNode* head2) {
    if (head1 == nullptr || head2 == nullptr) {
        return nullptr;
    }
    ListNode* cur1 = haveLoop2(head1);
    ListNode* cur2 = haveLoop2(head2);
    if (cur1 == nullptr && cur2 == nullptr) {
        return noLoop(head1, head2);
    }
    if (cur1 != nullptr && cur2 != nullptr) {
        return bothLoop(head1, cur1, head2, cur2);
    }
    return nullptr;
}

题目四 复制含有随机指针节点的链表

复制含有随机指针节点的链表

笔试做法:利用map

ListNode* copyListWithRand1(ListNode* head) {
	unordered_map<ListNode*, ListNode*> umap;
	ListNode* cur = head;
	while (cur != nullptr) {  // 将所有元素放到umap中
		umap.insert({ cur, new ListNode(cur->val * 10) });
		cur = cur->next;
	}
	cur = head;
	while (cur != nullptr) {
		umap[cur]->next = umap[cur->next];
		umap[cur]->rand = umap[cur->rand];
		cur = cur->next;
	}
	return umap.at(head);
}

面试做法

random
ListNode* copyListWithRand1(ListNode* head) {
	unordered_map<ListNode*, ListNode*> umap;
	ListNode* cur = head;
	while (cur != nullptr) {  // 将所有元素放到umap中
		umap.insert({ cur, new ListNode(cur->val * 10) });
		cur = cur->next;
	}
	cur = head;
	while (cur != nullptr) {
		umap[cur]->next = umap[cur->next];
		umap[cur]->rand = umap[cur->rand];
		cur = cur->next;
	}
	return umap[head];
}


ListNode* copyListWithRand2(ListNode* head) {
	if (head == nullptr) {
		return nullptr;
	}
	ListNode* cur = head;
	while (cur != nullptr) {  // 复制节点
		ListNode* newNode = new ListNode(cur->val * (10));
		ListNode* temp = cur->next;
		newNode->next = cur->next;
		cur->next = newNode;
		cur = temp;
	}
	cur = head;
	while (cur != nullptr) {  // 构造节点random指针
		if (cur->rand != nullptr) {
			cur->next->rand = cur->rand->next;
		}
		else {
			cur->next->rand = nullptr;
		}
		if (cur->next->next == nullptr) {
			break;
		}
		cur = cur->next->next;
	}
	cur = head;

	ListNode* temp = head->next;

	while (cur != nullptr) { // 删除原来节点连线
		ListNode* p = cur->next;
		cur->next = nullptr;
		if(p->next == nullptr) {
			break;
		}
		cur = p->next;
		p->next = cur->next;
	}
	return temp;
}

P7二叉树

基础知识

定义

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

存储方式

  • 链表
  • 数组

遍历方式

深度优先,广度优先

前中后指的是的位置,其余都按照左右排列

  1. 前序

    [中左右]

    class Solution {
    public:
        vector<int> preorderTraversal(TreeNode* root) {
            vector<int> result;
            traversal(root, result);
            return result;
        }
        void traversal(TreeNode* cur, vector<int>& result) {
            if (cur == nullptr) {
                return;
            }
            result.push_back(cur->val);
            traversal(cur->left, result);
            traversal(cur->right, result);
        }
    };
    
    class Solution {
    public:
        vector<int> preorderTraversal(TreeNode* root) {
            stack<TreeNode*> st;
            vector<int> result;
            if (root == NULL) return result;
            st.push(root);
            while (!st.empty()) {
                TreeNode* node = st.top();                       // 中
                st.pop();
                result.push_back(node->val);
                if (node->right) st.push(node->right);           // 右(空节点不入栈)
                if (node->left) st.push(node->left);             // 左(空节点不入栈)
            }
            return result;
        }
    };
    
  2. 中序

    [左中右]

    void traversal(TreeNode* cur, vector<int>& vec) {
        if (cur == NULL) return;
        traversal(cur->left, vec);  // 左
        vec.push_back(cur->val);    // 中
        traversal(cur->right, vec); // 右
    }
    
    class Solution {
    public:
        vector<int> inorderTraversal(TreeNode* root) {
            vector<int> result;
            stack<TreeNode*> st;
            TreeNode* cur = root;
            while (cur != NULL || !st.empty()) {
                if (cur != NULL) { // 指针来访问节点,访问到最底层
                    st.push(cur); // 将访问的节点放进栈
                    cur = cur->left;                // 左
                } else {
                    cur = st.top(); // 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
                    st.pop();
                    result.push_back(cur->val);     // 中
                    cur = cur->right;               // 右
                }
            }
            return result;
        }
    };
    
    
  3. 后序

    [左右中]

    void traversal(TreeNode* cur, vector<int>& vec) {
            if (cur == nullptr) {
                return;
            }
            traversal(cur->left, vec);
            traversal(cur->right, vec);
            vec.push_back(cur->val);
        }
    
  4. 宽度优先遍历bfs

    利用队列,按照宽度打印,将头节点入队后,弹出,再进左再进右

    //宽度优先遍历
    void bfs(Node* head) {
    	if (head != nullptr) {
    		queue<Node*>q;
    		q.push(head);
    		while (!q.empty()) {
    			head = q.front();
    			q.pop();
    			cout << head->val << " ";
    			if (head->left != nullptr) {
    				q.push(head->left);
    			}
    			if (head->right != nullptr) {
    				q.push(head->right);
    			}
    		}
    	}
    }
    
    

题目求一棵二叉树的宽度

思路1:利用哈希表

利用bfs按照宽度遍历,则使用queue,将map中放入Node和其对应的行数

int getMaxWidth(Node* head) {
        queue<Node*> myque; //队列
        unordered_map<Node*, int> umap; // 设置哈希表,分别存放二叉树结点以及结点所在层数
        int curlevel = 1; //当前层
        int nodes = 0;//该层数目
        int max = -1;//最大 实时更新
        umap[head] = 1;
        myque.push(head);
        Node* cur = head;
        while (!myque.empty()) {
            cur = myque.front();
            myque.pop();
            if (cur->left != nullptr) {
                umap[cur->left] = curlevel + 1;
                myque.push(cur->left);
            }
            if (cur->right != nullptr) {
                umap[cur->right] = curlevel + 1;
                myque.push(cur->right);
            }
            if (umap[cur] == curlevel) {   //当该节点层数与当前层数相同,则节点数目++
                nodes++;
            }
            else {    // 若不同,则更新max,更新curlevel
                max = max > nodes ? max : nodes;
                curlevel = umap[cur];
                nodes = 1;
            }
        }
        return max;
    }

思路2 不用hashmap。需要用到几个变量:curend:当前层最后一个节点、nextend:下一层最后一个节点(始终是最近进栈的节点,换层的时候要置空)、curlevel当前层已经发现的节点数

二叉树宽度
int getMaxWidth02(Node* head) {
    if (head == nullptr) {
        return 0;
    }
    queue<Node*> myque;
    Node* curend = head;
    Node* nextend = nullptr;
    int nodes = 1;
    int maxNodes = INT32_MIN;
    myque.push(head);
    Node* cur = nullptr;
    while (!myque.empty()) {
        cur = myque.front();
        myque.pop();
        if (cur->left != nullptr) {
            myque.push(cur->left);
            nextend = cur->left;
        }
        if (cur->right != nullptr) {
            myque.push(cur->right);
            nextend = cur->right;
        }
        nodes++;
        if (cur == curend) {
            curend = nextend;
            nextend = nullptr;
            maxNodes = max(maxNodes, nodes);
            nodes = 0;
        }
    }
    return maxNodes;
}

判断 搜索二叉树

定义
  • 是一个有序树
  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉排序树
判断方式

利用中序排列,若为升序,则为搜索二叉树

判断 完全二叉树

定义

在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。

判断方式
  • 利用层序遍历
  • 若某个节点有右孩子,没左孩子,则返回false
  • 在满足上一条情况时,第一次遇到左右节点不全情况,则后续必须为叶节点
bool isCBT(Node* head) {
    if (head == nullptr) {
        return true;
    }
    queue<Node*> myque;
    myque.push(head);
    bool leaf = false;
    Node* cur = nullptr;
    while (!myque.empty()) {
        cur = myque.front();
        myque.pop();
        if ((leaf && (cur->right != nullptr || cur->left != nullptr)) || (cur->left == nullptr && cur->right != nullptr)) {//某个节点有右孩子,没左孩子;第一次遇到左右节点不全情况,则后续必须为叶节点
            return false;
        }
        if (cur->left != nullptr) {
            myque.push(cur->left);
        }
        if (cur->right != nullptr) {
            myque.push(cur->right);
        }
        else {
            leaf = true;
        }
    }
    return true;
}

判断 满二叉树

  • 麻烦方法:求出二叉树深度,再求出节点个数,若满足2^n-1则为满二叉树
  • 利用套路向左树要深度和节点数,向右数要深度和节点数看书否满足标准
//判断一颗二叉树是否是满二叉树
struct ReDa {
	int depth;
	int nodes;
	bool isFbt;
	ReDa(int depth, int nodes, bool isFbt) {
		this->depth = depth;
		this->nodes = nodes;
		this->isFbt = isFbt;
	}
};
ReDa isFull(Node* head) {
	if (head == nullptr) {
		return ReDa(0, 0, true);
	}
	ReDa leftData = isFull(head->left);
	ReDa rightData = isFull(head->right);
	int depth = max(leftData.depth, rightData.depth) + 1;
	int nodes = leftData.nodes + rightData.nodes + 1;
	bool isFbt = (nodes == (int)pow(2, depth) - 1) ? true : false;

	return ReDa(depth, nodes, isFbt);
}

判断 平衡二叉树

平衡二叉树:任意一个节点,左树的高度和右树的高度差不超过1

套路:求解一个二叉树问题时,我可以向我的左树要信息,可以向我的右树要信息的情况下,列出可能性,向上层递归返回我这一层的信息。可以解决一切树型DP(动态规划)的问题,二叉树中最难的问题。

假设以x为头的子树,判断他是不是平衡二叉树,可以向我的左树要信息,可以向我的右树要信息的情况下,罗列可能性;1) 左子树要是平衡二叉树;2) 右子树要是平衡二叉树;3) 左子树、右子树的高度差小于2;这里只有一种可能性:以上三个条件都成立。所以左树需要给我:它是否是平的和高度;右树需要给我:它是否是平的和高度。

利用递归(黑盒)去解

//***************************平衡二叉树***************************//
struct ReturnType {
    bool isBalance;
    int height;
    ReturnType(bool isBalance, int height) : isBalance(isBalance), height(height) {}
};
ReturnType process(Node* head) {
    if (head == nullptr) {
        return ReturnType(true, 0);
    }
    ReturnType left = process(head->left);
    ReturnType right = process(head->right);
    bool isBalance = (left.isBalance && right.isBalance) && (abs(left.height - right.height) < 2);
    int height = max(left.height, right.height) + 1;
    return ReturnType(isBalance, height);
}
bool isBalance(Node* head) {
    if (process(head).isBalance) {
        cout << "是";
    }
    else {
        cout << "不是";
    }
    return process(head).isBalance;
}

给定两个二叉树的节点node1和node2,找到他们的最低公共祖先节点

力扣236

  • 思路1:视频1:33分前
    1. 首先定义unordered_map为fathermap,存储所有结点的father结点至fathermap中
    2. 定义unordered_set,将node1所有父结点放入set中,遍历node2,若其父节点与set中重合,则找到最低公共祖先结点
// 生成fathermap,即每个节点对应的father记录在map中
    void process(Node* head, unordered_map<Node*, Node*> fathermap) {
        if (head == nullptr) {
            return;
        }
        fathermap.insert({ head->left, head });
        fathermap.insert({ head->right, head });
        process(head->left, fathermap);
        process(head->right, fathermap);
    }

    Node* lowestAncestor01(Node* head, Node* o1, Node* o2) {
        if (head == o1 && o1 == o1) {
            return head;
        }
        if (head == nullptr || o1 == nullptr || o2 == nullptr) {
            return nullptr;
        }
        unordered_map<Node*, Node*> fathermap;
        process(head, fathermap);
        fathermap.insert({ head, head });
        unordered_set<Node*> uset;
        Node* cur = o1;
        while (fathermap[cur] != cur) { // 没有到顶
            uset.insert(cur);
            cur = fathermap[cur];
        }
        uset.insert(head);
        cur = o2;
        while (fathermap[cur] != cur) {
            if (uset.find(cur) != uset.end()) {
                return cur;
            }
            cur = fathermap[cur];
        }
        return nullptr;
    }
  • 思路2

    • o1可能为o2的LCA
    • o1可能和o2不互为,需要往上找才行

    两者为空返回空,两者有值返回头

    public static Node lowestAncestor(Node head, Node o1, Node o2) {
    		if (head == null || head == o1 || head == o2) {
    			return head;
    		}
    		Node left = lowestAncestor(head.left, o1, o2);
    		Node right = lowestAncestor(head.right, o1, o2);
    		if (left != null && right != null) {
    			return head;
    		}
    		return left != null ? left : right;
    	}
    

在二叉树中找到一个节点的后继节点

思路

中序遍历特性,判断是否有右儿子,有的话后继为其最底左儿子,若没有则后继为parent中使其成为左树分支的最近一个parent,最后一个节点为null

public static Node getSuccessorNode(Node node) {
		if (node == null) {
			return node;
		}
		if (node.right != null) {
			return getLeftMost(node.right);
		} else {
			Node parent = node.parent;
			while (parent != null && parent.left != node) {
				node = parent;
				parent = node.parent;
			}
			return parent;
		}
	}

	public static Node getLeftMost(Node node) {
		if (node == null) {
			return node;
		}
		while (node.left != null) {
			node = node.left;
		}
		return node;
	}

二叉树的序列化和反序列化

就是内存里的一棵树如何变成字符串形式,又如何从字符串形式变成内存里的树

//***************************二叉树的序列化和反序列化***************************// 
string serialByPre(Node* head) {    //先序
    if (head == nullptr) {
        return "#!";
    }
    string res;
    res = to_string(head->val) + "!";
    res += serialByPre(head->left);
    res += serialByPre(head->right);
    return res;
}
queue<string> splitString(string str, char spS) { // split 字符串 重要!!!
    queue<string> q;
    string s = " ";
    for (auto ch : str) {
        if (ch == spS) {
            q.push(s);
            s = " ";
        }
        else {
            s = s + ch;
        }
    }
    return q;
}
Node* reconPreOrder(queue<string> myque) {
    string str = myque.front();
    myque.pop();
    if (str == "#") {
        return nullptr;
    }
    Node* head = new Node(stoi(str));
    head->left = reconPreOrder(myque);
    head->right = reconPreOrder(myque);
    return head;
}
Node* antiserialByPre(string str) {
    if (str.length() < 3) {
        return nullptr;
    }
    queue<string> myque = splitString(str, '!');
    return reconPreOrder(myque);
}

剑指offer 48

折纸问题

void fold(int i, int N, bool updown) {
        if (i > N) {
            return;
        }
        fold(i + 1, N, true);
        updown ? cout << "down " : cout << "up ";
        fold(i + 1, N, false);
    }

P9图

模板

class Node;//两个类相互包含,需要提前声明一下

class Edge {
public:
	int weight;
	Node* from;//有向图
	Node* to;
	Edge(int weight, Node* from, Node* to) {
		this->weight = weight;
		this->from = from;
		this->to = to;
	}
};

class Node {
public:
	int val;
	int in;//入度
	int out;//出度
	vector<Node*> nexts;
	vector<Edge*> edges;//由当前点发散出去的边

	Node(int val) {
		this->val = val;
		in = 0;
		out = 0;
	}
};

class Graph {
public:
	unordered_map<int, Node*>nodes;//key:点的编号;value:实际的点
	unordered_set<Edge*>edges;
};

宽度(广度)优先遍历BFS

  • 利用队列实现
  • 从源节点开始依次按照宽度进队列,然后弹出
  • 每弹出一个点,把该节点所有没有进过队列的邻接点放入队列
  • 直到队列变空
void BreadthFirst_Traveral(Node* node) {
        if (node == nullptr) {
            return;
        }
        queue<Node*> myque;  //队列来记录
        unordered_set<Node*> myset;//set来记录是否访问过
        myque.push(node);
        myset.insert(node);
        Node* cur = nullptr;
        while (!myque.empty()) {//首先将queue中第一个弹出,再将该结点next中未记录的放入queue并记录
            cur = myque.front();
            myque.pop();
            cout << cur->value << ' ';//弹出时打印
            for (auto iter : cur->next) {
                if (myset.find(iter) == myset.end()) {
                    myque.push(iter);
                    myset.insert(iter);
                }
            }
        }
    }

深度优先遍历xDFS

  • 利用栈实现
  • 从源节点开始把节点按照深度放入栈,然后弹出
  • 每弹出一个点,把该节点下一个没有进过栈的邻接点放入栈
  • 直到栈变空
void DepthFirst_Traveral(Node* node) {//深度优先遍历,相当于一股脑先遍历到头
        if (node == nullptr) {
            return;
        }
        unordered_set<Node*> myset;//set记录是否访问过
        stack<Node*> myst;//stack记录Node*
        myset.insert(node);
        myst.push(node);
        cout << node->value;//第一次放入时打印
        Node* cur = nullptr;
        while (!myst.empty()) {
            cur = myst.top();
            myst.pop();
            for (auto p : cur->next) {
                if (myset.find(p) == myset.end()) {
                    cout << p->value;
                    myset.insert(p);
                    myst.push(cur);
                    myst.push(p);
                    break;//找到1个就行
                }
            }
        }
    }

拓扑排序算法

适用范围:要求有向图,且有入度为0的节点,且没有环

力扣207.课程表

  1. 使用map存储节点及其入度,使用queue存储入度为0的点
  2. 遍历图中所有结点,把入度为0的放入queue中
  3. 弹出queue中第一个入度为0的结点并记录
  4. 遍历入度为0的next结点,并将其入度-1,若有入度为0则放到queue里面
  5. 队列不位空,重复34
vector<Node*> Topu(Graph graph) {
    unordered_map<Node*, int> umap;
    queue<Node*> myque;
    for (auto p : graph.nodes) {
        umap.insert({ p.second, p.second->in });// 利用map记录节点及其入度
        if (p.second->in == 0) {
            myque.push(p.second);// 将入度为0的节点加入queue
        }
    }
    vector<Node*> res;
    while (!myque.empty()) {
        Node* cur = myque.front();
        myque.pop();
        res.push_back(cur);
        for (auto p : cur->nexts) {
            umap[p]--;
            if (umap[p] == 0) {
                myque.push(p);
            }
        }
    }
    return res;
}

最小生成树

定义:使无向图所有节点连通且权重最小的边集,是最小权重生成树的简称。

算法:kruskal算法、prim算法

kruskal算法

要求:无向图

  • 从最小边开始搜索,若没形成环加上,否则不加,继续循环

核心代码 改堆

	public static class EdgeComparator implements Comparator<Edge> {
		public int compare(Edge o1, Edge o2) {
			return o1.weight - o2.weight;
		}
	}
	public static Set<Edge> kruskalMST(Graph graph) {
		UnionFind unionFind = new UnionFind();
		unionFind.makeSets(graph.nodes.values());
		PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());
		for (Edge edge : graph.edges) {
			priorityQueue.add(edge);
		}
		Set<Edge> result = new HashSet<>();
		while (!priorityQueue.isEmpty()) {
			Edge edge = priorityQueue.poll();
			if (!unionFind.isSameSet(edge.from, edge.to)) {
				result.add(edge);
				unionFind.union(edge.from, edge.to);
			}
		}
		return result;
	}

并查集

	public static class UnionFind {
		private HashMap<Node, Node> fatherMap;
		private HashMap<Node, Integer> rankMap;

		public UnionFind() {
			fatherMap = new HashMap<Node, Node>();
			rankMap = new HashMap<Node, Integer>();
		}

		private Node findFather(Node n) {
			Node father = fatherMap.get(n);
			if (father != n) {
				father = findFather(father);
			}
			fatherMap.put(n, father);
			return father;
		}

		public void makeSets(Collection<Node> nodes) {
			fatherMap.clear();
			rankMap.clear();
			for (Node node : nodes) {
				fatherMap.put(node, node);
				rankMap.put(node, 1);
			}
		}

		public boolean isSameSet(Node a, Node b) {
			return findFather(a) == findFather(b);
		}

		public void union(Node a, Node b) {
			if (a == null || b == null) {
				return;
			}
			Node aFather = findFather(a);
			Node bFather = findFather(b);
			if (aFather != bFather) {
				int aFrank = rankMap.get(aFather);
				int bFrank = rankMap.get(bFather);
				if (aFrank <= bFrank) {
					fatherMap.put(aFather, bFather);
					rankMap.put(bFather, aFrank + bFrank);
				} else {
					fatherMap.put(bFather, aFather);
					rankMap.put(aFather, aFrank + bFrank);
				}
			}
		}
	}

prim算法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cMGpLR4H-1671605563886)(C:\Users\MSI-NB\Desktop\左程云\图片\Prim.png)]

unordered_set<Edge*> Graph::MiniScanTree_Prim(Graph graph) {
    priority_queue<Edge*, vector<Edge*>, prior> myqueue;
    unordered_set<Node*> uset;
    unordered_set<Edge*> result;
    for (auto node : graph.nodes) {
        if (uset.find(node.second) == uset.end()) {//如果发现结点不在set中执行
            uset.insert(node.second);//首先set中插入结点
            for (auto edges : node.second->edges) {//将所有对应的edges放入小根堆中
                myqueue.push(edges);
            }
            while (!myqueue.empty()) {//当myqueue不为空
                Edge* cur_edge = myqueue.top();//弹出最小边
                myqueue.pop();
                if (uset.find(cur_edge->to) == uset.end()) {//若最小边对应的to没被选中过,执行,否则继续弹出第二小
                    result.insert(cur_edge);//将该较小边放入result中
                    uset.insert(cur_edge->to);//将较小边对应的to放入set中
                    for (auto newEdges : cur_edge->to->edges) {//解锁to点的所有边
                        myqueue.push(newEdges);
                    }
                }
            }
        }
    }
    return result;
}

单元最短路径算法—Dijkstra算法

适用范围:不存在累加权值为负的环

初始化一个节点到其他节点距离表,该表到自己的距离为0,到其他节点的距离都为正无穷,每一次在表中选择距离最小的点A,考察从A出发的边能否使得起始点到其他点的距离变小,如果存在这样的边,则更新距离表,使用完A之后,A到起始点的距离确定,不再修改;再选择次小的点……

Node* getMiniNode(unordered_set<Node*> uset, unordered_map<Node*, int> distanceMap)
{
    Node* miniNode = nullptr;
    for (pair<Node*, int> p : distanceMap) {
        if (uset.find(p.first) == uset.end()) {
            if (miniNode != nullptr) {
                miniNode = p.second < distanceMap[miniNode] ? p.first : miniNode;
            }
            else {
                miniNode = p.first;
                miniNode = p.first;
            }
        }
    }
    return miniNode;
}
unordered_map<Node*, int> Dijkstral(Node* head) {
    unordered_set<Node*> uset;// 存放已经遍历过的节点
    unordered_map<Node*, int> distanceMap;
    distanceMap.insert({ head, 0 });
    Node* miniNode = getMiniNode(uset, distanceMap);
    while (miniNode != nullptr) {
        int distance = distanceMap[miniNode];
        for (Edge* p : miniNode->edges) {
            if (uset.find(p->to) == uset.end()) {
                distanceMap.insert({ p->to, p->weight });
            }
            else {
                distanceMap[p->to] = min(distanceMap[p->to], distance + p->weight);
            }
        }
        uset.insert(miniNode);
        miniNode = getMiniNode(uset, distanceMap);
    }
    return distanceMap;
}

P10前缀树和贪心算法

前缀树

定义:

class TrieNode {
public:
    int pass;//有多少个走过该结点
    int end;// 有多少个在该结点结束
    vector<TrieNode*> next;// 存放TrieNode*的向量数组
    TrieNode() {//构造函数
        this->pass = 0;
        this->end = 0;
        this->next = vector<TrieNode*>(26, nullptr); //26个元素,每个为空指针s
    }
};

生成前缀树

void Trie::insert(string word) {
    if (word.length() == 0) {
        return;
    }
    TrieNode* node = root;                             // 首先构造头结点
    node->pass++;                                      //头结点的pass++
    for (char ch : word) {                             // 遍历word里的每个字符
        if (node->next[ch - 'a'] == nullptr) {         // 若next没有对应结点,则新建
            node->next[ch - 'a'] = new TrieNode();
        }
        node = node->next[ch - 'a'];
        node->pass++;
    }
    node->end++;
}
struct TrieNode {
    int pass;
    int end;
    vector<TrieNode*> nexts;
    TrieNode() {
        this->pass = 0;
        this->end = 0;
        this->nexts = vector<TrieNode*>(26, nullptr);
    }
};


class Trie {
private:
    TrieNode* root;
public:
    Trie() {
        this->root = new TrieNode();
    }

    void insert(string str) {
        if (str.length() == 0) {
            return;
        }
        TrieNode* node = root;
        node->pass++;
        for (char ch : str) {
            int index = ch - 'a';
            if (node->nexts[index] == nullptr) {
                node->nexts[index] = new TrieNode();
            }
            node->nexts[index]->pass++;
            node = node->nexts[index];
        }
        node->end++;
        cout << "添加成功" << str << endl;
    }

    int search(string str) {
        if (str.length() == 0) {
            return 0;
        }
        TrieNode* node = root;
        for (char ch : str) {
            int index = ch - 'a';
            if (node->nexts[index] == nullptr) {
                cout << "找到" << str << "有" << "0" << "次" << endl;
                return 0;
            }
            else {
                node = node->nexts[index];
            }
        }
        cout << "找到" << str << "有" << node->end << "次" << endl;
        return node->end;
    }

    int prefixNumber(string str) {
        if (str.length() == 0) {
            return 0;
        }
        TrieNode* node = root;
        for (char ch : str) {
            int index = ch - 'a';
            if (node->nexts[index] == nullptr) {
                cout << "以" << str << "为前缀有" << "0" << "次" << endl;
                return 0;
            }
            else {
                node = node->nexts[index];
            }
        }
        cout << "以" << str << "为前缀有" << node->pass << "次" << endl;
        return node->pass;
    }

    void deleteWord(string str) {
        if (search(str)) {// 添加过该字符串
            TrieNode* node = root;
            root->pass--;
            int index = -1;// 记录第一个pass为0的位置
            TrieNode* pre = nullptr; // 记录pass最后一个不为0的指针
            stack<TrieNode*> st;
            bool needDetele = false;
            for (int i = 0; i < str.length(); i++) {
                int j = str[i] - 'a';
                node->nexts[j]->pass--;
                if (node->nexts[j]->pass == 0) {
                    needDetele = true;
                    index = index == -1 ? i : index;
                    pre = pre == nullptr ? node : pre;
                    st.push(node->nexts[j]);
                }
                node = node->nexts[j];
            }
            node->end--;
            if (needDetele) {
                pre->nexts[str[index] - 'a'] = nullptr;
                while (!st.empty()) {
                    delete st.top();
                    st.pop();
                }
            }
        }
    }
};

贪心算法

在某一个标准下,优先考虑最满足标准的样本,最后考虑最不满足标准的样本,最终得到一个答案的算法,叫作贪心算法。

也就是说,不从整体最优上加以考虑,所做出的是在某种意义上的局部最优解。

题目1会议室

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CEIcmsQd-1671605563886)(C:\Users\MSI-NB\Desktop\左程云\图片\会议室.jpg)]

贪心方法:按照结束时间来排序,首先选取结束时间最早的,再依次跟上

class Program {
public:
    double start;
    double end;
    Program(double start, double end) {
        this->start = start;
        this->end = end;
    }
};

bool cmp(Program* o1, Program* o2) {
    return o1->end < o2->end;
}

class Solution {
public:
    int bestArrange(vector<Program*> program, int start) {
        sort(program.begin(), program.end(), cmp);
        cout << "经过end排序后" << endl;
        for (int i = 0; i < program.size(); i++) {
            cout << "start:" << program[i]->start << " end:" << program[i]->end << endl;
        }
        int res = 0;
        cout << "最多有" << "分别为" << endl;
        for (int i = 0; i < program.size(); i++) {
            if (start <= program[i]->start) {
                res++;
                start = program[i]->end;
                cout << "start:" << program[i]->start << " end:" << program[i]->end << endl;
            }
        }
        cout << "最多有" << res << "个会议可以进行" << endl;
        return res;
    }
};

题目2字典序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l0NdJPqM-1671605563887)(C:\Users\MSI-NB\Desktop\左程云\图片\字典.jpg)]

字典2
  • 方法:a+b小于等于b+a则交换位置
  • 将sort函数的cmp函数进行以上规则的改写

题目三金条切割

利用哈夫曼树

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0HaUan6Z-1671605563887)(C:\Users\MSI-NB\Desktop\左程云\图片\金条.jpg)]

哈夫曼算法描述:

  • 根据给定权值n构建二叉树集合f,其中每个二叉树只有带权为w的根节点,没有左右子树
  • 再f中选取两个权值最小的构建新的二叉树,删除老的两颗,在f中加入新的二叉树
  • 重复步骤,直至只剩一棵二叉树

题目4项目收益

项目收益

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8rXbPO9Q-1671605563887)(C:\Users\MSI-NB\Desktop\左程云\图片\项目收益解答.png)]

思路:

  • 在小根堆中放入所有的项目,按照项目需要花费的多少排序
  • 从小根堆中拿出项目花费小于初始资金的项目放入大根堆中,按照利润大小排序
  • 则每次从大根堆中取出元素则为下个需要进行的项目
  • 若大根堆中没有项目,或者所做项目之和超过k则停止

题目五中位数

一个数据流中,随时可以取得中位数

  • 准备一个大根堆,一个小根堆
  • 第一个数直接放入
  • 第二个数cur开始,若cur比大根堆第一个数小,则放入大根堆,反之放入小根堆
  • 若大小根堆数量差距大于1,则将size大的弹一个放到size小的去
  • 若n为奇数,则中位数为size大第一个数,若为偶数,则中位数为两个堆的顶

P11暴力递归

暴力递归就是尝试

  • 把问题转化为规模缩小了的同类问题的子问题
  • 有明确的不需要继续进行递归的条件(base case)
  • 有当得到了子问题的结果之后的决策过程
  • 不记录每一个子问题的解

汉诺塔问题

  • 设有i个待移动,from, to, other
    1. 将i-1个由from移到other中
    2. 将第i个由from移到to中
    3. 将i-1个由other移到to中
int hanoi(int i, string from, string to, string other) {
    static int count = 0;
    if (i == 1) {                                       // base case
        cout << from << "->" << to << endl;
        count++;
    }
    else {
        // 首先,先将i-1个由from放到other上
        hanoi(i - 1, from, other, to);
        // 再将最后第i个由from放到to上
        cout << from << "->" << to << endl;
        count++;
        // 再将i-1个由other放到to上
        hanoi(i - 1, other, to, from);
    }
    return count;
}

打印子序列

题目:打印一个字符串的全部子序列,包括空字符串

字符串子序列
void printAllSubsquence(int i, string str) {
    if (i == str.length()) {
        cout << "(" << str << ")";
        return;
    }
    printAllSubsquence(i + 1, str);// 第i个不为0递归
    string temp = str;
    str[i] = 0;
    printAllSubsquence(i + 1, str);// 第i个为0递归
    str = temp;
}

打印一个字符串的全部排列,要求不要出现重复的排列

字符串排列无重复
  • 第i个字符开始,前i-1个已经定下来了,交换j和i字符的位置,再进行递归
vector<string> Permutation(string str) {
    vector<string> res;
    if (str.length() == 0) {
        return res;
    }
    process(0, str, res);
    return res;
}

void process(int i, string str, vector<string> &res) {
    if (i == str.length()) {
        res.push_back(str);
        return;
    }
    unordered_set<char> uset;// 不能设置为全局变量,应该设置为当前层的变量用来存目前已经有多少入库
    for (int j = i; j < str.length(); j++) {
        if (uset.find(str[j]) == uset.end()) {
            uset.insert(str[j]);
            swapstr(str, i, j);
            process(i + 1, str, res);
            swapstr(str, i, j);
        }
    }
}

void swapstr(string &s, int i, int j) {// 需要对参数进行修改则需要加入&号
    char temp = s[j];
    s[j] = s[i];
    s[i] = temp;
}

拿牌

拿牌

逆序栈,不用额外数据结构,只使用递归函数

void reverse(stack<int> st) {
    if (st.empty()) {
        return;
    }
    int result = getAndRemoveLastElement(st);
    reverse(st);
    st.push(result);
}


int getAndRemoveLastElement(stack<int> st) { // 返回栈底元素,并使栈整体下移
    int result = st.top();
    st.pop();
    if (st.empty()) {
        return result;
    }
    else {
        int last = getAndRemoveLastElement(st);
        st.push(result);
        return last;
    }
}
返回栈

解码方法

解码方法
  • 在i字符中,前i-1都已经解码分配完成
  • 第i个,若为1则可以分为单个和与后者结合;若为2要看与后者结合会不会超过26
  • 其余3-9只能与自己结合
  • 进行递归,设置好base case
int process(string str, int i) {
        if (i == str.length()) {
            return 1;
        }
        if (str[i] == '0') {
            return 0;
        }
        else if (str[i] == '1') {
            int res = process(str, i + 1);
            if ((i + 1) < str.length()) {
                res += process(str, i + 2);
                cout << "!";
            }
            return res;
        }
        else if (str[i] == '2') {
            int res = process(str, i + 1);
            if ((i + 1) < str.length() && (str[i + 1] >= '0' && str[i + 1] <= '6')) {
                res += process(str, i + 2);
            }
            return res;
        }

        return process(str, i + 1);

    }
    int numDecodings(string s) {
        if (s.length() == 0) {
            return 0;
        }
        return process(s, 0);
    }

背包问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kxmaxiL3-1671605563888)(C:\Users\MSI-NB\Desktop\左程云\图片\背包问题.PNG)]

// i-1个背包已经选完,第i个背包开始
    int process(vector<int> value, vector<int> weight, int i, int alreadywight, int bag) {
        if (i == weight.size()) {
            return 0;
        }
        if ((weight[i] + alreadywight) > bag) {
            return process(value, weight, i + 1, alreadywight, bag);
        }
        else {
            return max(
                process(value, weight, i + 1, alreadywight, bag),
                value[i] + process(value, weight, i + 1, alreadywight + weight[i], bag)
            );
        }
    }

N皇后问题

N皇后
bool isValid(vector<int> record, int i, int j) {
        for (int k = 0; k < i; k++) {   // 只需要遍历i前面的record即可
            if (j == record[k] || (abs(i - k) == abs(record[k] - j))) {
                return false;
            }
        }
        return true;
    }

    int process1(int i, vector<int>& record, int n) {// 传统方法 , 其中record[i] = j表示,第i行的第j列已经有皇后了
        if (i == n) {
            return 1;
        }
        int res = 0;
        for (int j = 0; j < n; j++) {   // 例:在第一行i=0时,统计每一列为N皇后时的res总和
            if (isValid(record, i, j)) {
                record[i] = j;
                res += process1(i + 1, record, n);
            }
        }
        return res;
    }

    int process2(int limit, int colLim, int leftDiaLim, int rightDiaLim) {// 使用位运算加速
        if (limit == colLim) {
            return 1;
        }
        int res = 0;
        int pos = 0;
        int mostRightone = 0;
        pos = limit & (~(colLim | leftDiaLim | rightDiaLim));   // 用1来表示可以放置皇后的点
        while (pos != 0) { // 当还有放皇后的位置
            mostRightone = pos & (~pos + 1); // 取出最右边的1
            pos = pos - mostRightone;
            res += process2(limit, colLim | mostRightone, (leftDiaLim | mostRightone) << 1, (rightDiaLim | mostRightone) >> 1); // 左极限为当前选择与上个做极限或并且左移一位
        }
        return res;
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值