【输入输出格式 - 算法笔记P19】
输入输出使用scanf和printf。(因为scanf和printf的运行速度比cin和cout快)
- 相异为1,相同为0。
- N0=N,NN=0。
- 满足交换律和结合律。
// 数组中有一个数只出现奇数次,其余都出现偶数次。请找出这个数。
// leetcode-136
// 时间复杂度O(n),空间复杂度O(1)
int findNum(vector<int>& nums){
int res = 0;
for(int n : nums){
res ^= n;
}
return res;
}
// 数组中有两个数只出现奇数次,其余都出现偶数次。请找出这两个数。
// leetcode-260
// 时间复杂度O(n),空间复杂度O(1)
vector<int> findNums(vector<int>& nums){
int eor = 0;
for(int n : nums){
eor ^= n;
}
// eor=a^b,且不等于0。
// eor&(eor的补码)找出eor二进制表示中为1的最低位。
// 判断eor是不是INT_MIN,如果等于INT_MIN,取反会溢出。
int right = eor == INT_MIN ? eor : eor & (-eor);
int one = 0; // 得到a或b。
for(int n : nums){
// 根据right位的不同分类异或。
if(n & right){
one ^= n;
}
}
vector<int> res;
res.push_back(one);
res.push_back(one^eor);
return res;
}
void swap(vector<int>& nums, int i, int j){
// 临时变量
int temp = nums[i];
num[i] = nums[j];
nums[j] = temp;
// 加减(定义了加减法的数据结构才能用)
nums[i] = nums[i] + nums[j];
nums[j] = nums[i] - nums[j];
nums[i] = nums[i] - nums[j];
// 异或(地址值i和j必须不同,但地址所存储的值可以一样(会将该地址所存储的值抹为0,相当于自己地址值里的内容跟自己地址值里的内容异或=0))
nums[i] = nums[i] ^ nums[j];
nums[j] = nums[i] ^ nums[j];
nums[i] = nums[i] ^ nums[j];
}
master公式
- T(N) = a*T(N/b) + O(N^d)
- 适用于子问题等规模的递归,求解母问题的时间复杂度。
- (1)log(b, a) > d => 复杂度为O( N^log(b, a) )
- (2)log(b, a) = d => 复杂度为O( N^d * logN)
- (3)log(b, a) < d => 复杂度为O( N^d )
#include
#include
...
int main(){
// 生成随机种子
srand((unsigned)time(NULL));
// 生成随机数[0, RAND_MAX]
cout << rand() << endl;
// [0, 1]
cout << rand() % 2 << endl;
cout << rand() / RAND_MAX << endl;
// [x, y]
cout << rand() % (y-x) + x << endl;
cout << (int)round(1.0 * rand() / RAND_MAX * (y - x) + x) << endl;
}
基于比较的排序
- 当前指向i位置,每次从i之后的序列中选出最小值,交换i和最小值。
- 时间复杂度O(n^2)
- 空间复杂度O(1)
- 不稳定
void selection(vector<int>& nums){
if(nums.size() < 2) return;
// 长度n,下标0~n-1
// 遍历i~n-2
for(int i = 0; i < nums.size()-1; i++){
int min = i;
// 遍历i+1~n-1
for(int j = i + 1; j < nums.size(); j++){
min = nums[j] < nums[min] ? j : min;
}
swap(nums, i, min);
}
}
- 从前往后,两两比较,大的往后放,每次比较后冒出一个最大的元素。
- 时间复杂度O(n^2)
- 空间复杂度O(1)
- 稳定
void bubbleSort(vector<int>& nums){
if(nums.size() < 2) return;
// i放每趟排序后冒出来的元素
for(int i = nums.size()-1; i > 0; i--){
// 遍历0~i-1
for(int j = 0; j < i; j++){
if(nums[j] > nums[j+1]) swap(nums, j, j+1);
}
}
}
- 将元素从后往前插入已排序序列中,比较直到前一个元素比元素小结束。
- 时间复杂度O(n^2)
- 空间复杂度O(1)
- 稳定
void insertSort(vector<int>& nums){
if(nums.size() < 2) return;
// i指向待插入元素。
for(int i = 1; i < nums.size(); i++){
// j指向i前面元素,比较j和j+1。
for(int j = i - 1; j >= 0 && arr[j] > arr[j+1]; j--){
swap(nums, j, j+1);
}
}
}
- 分成左右两序列,使左右序列分别有序;合并左右序列,小的插入result,指针后移。
- 时间复杂度O(NlogN)
- 空间复杂度O(N)
- 稳定
void merge(vector<int>& arr, int L, int mid, int R){
vector<int> result(R-L+1);
// 数组版本
// int result[R-L+1];
int i = 0;// 指向result数组当前要放入元素的位置
int p1 = L;// 指向左序列当前遍历到哪里
int p2 = mid + 1;// 指向右序列当前遍历到哪里
while(p1 <= M && p2 <= R){
// 注意是<=(相等时优先把左序列插入)
result[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
// 遍历结束还有元素未插入
while(p1 <= M){
result[i++] = arr[p1++];
}
while(p2 <= R){
result[i++] = arr[p2++];
}
for(int j = 0; j < i; j++){
arr[L+j] = result[j];
}
}
void process(vector<int>& arr, int L, int R){
if(L == R) return;
//mid = L + (R-L)/2;为了防止溢出,mid = L + (R-L)/2;位运算更快。
int mid = L + ((R - L) >> 1);
process(arr, L, mid);
process(arr, mid+1, R);
merge(arr, L, mid, R);
}
void mergeSort(vector<int>& nums){
if(nums.size() < 2) return;
process(nums, 0, nums.size()-1);
}
// 应用
// 小和问题:在一个数组中,遍历每个数,把每个数左边比当前数小的数累加起来,叫做这个数组的小和。
int merge(vector<int>& arr, int L, int mid, int R){
vector<int> result(R-L+1);
int i = 0;
int p1 = L;
int p2 = mid+1;
int res = 0;
while(p1 <= mid && p2 <= R){
res += arr[p1] < arr[p2] ? (R-p2+1) * arr[p1] : 0;
// 注意是<(优先让右序列插入,保证p2指向正确、小和正确)
result[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while(p1 <= mid){
result[i++] = arr[p1++];
}
while(p2 <= R){
result[i++] = arr[p2++];
}
for(int j = 0; j < i; j++){
arr[L+j] = result[j];
}
return res;
}
int process(vector<int>& arr, int L, int R{
if(L == R) return 0;
int mid = L + ((R-L)>>1);
return process(nums, L, mid)
+ process(nums, mid+1, R)
+ merge(nums, L, mid, R);
}
int smallSum(vector<int>& nums){
if(nums.size() < 2) return 0;
return process(nums, 0, nums.size()-1);
}
// 逆序对问题:在一个数组中,左边的数如果比右边的数大,则这两个数构成一个逆序对,请求出逆序对的总数。
// leetcode-剑指offer51
int merge(vector<int>& nums, int l, int mid, int r){
vector<int> result(r-l+1);
int i = 0;
int p1 = l;
int p2 = mid+1;
int res = 0;
while(p1 <= mid && p2 <= r){
res += nums[p1] > nums[p2] ? r-p2+1 : 0;
result[i++] = nums[p1] > nums[p2] ? nums[p1++] : nums[p2++];
}
while(p1 <= mid){
result[i++] = nums[p1++];
}
while(p2 <= r){
result[i++] = nums[p2++];
}
for(p1 = 0; p1 < i; p1++){
nums[l+p1] = result[p1];
}
return res;
}
int process(vector<int>& nums, int l, int r) {
if(l == r) return 0;
int mid = l + ((r-l)>>1);
return process(nums, l, mid)
+ process(nums, mid+1, r)
+ merge(nums, l, mid, r);
}
int reversePairs(vector<int>& nums) {
if(nums.size() < 2) return 0;
return process(nums, 0, nums.size()-1);
}
- 时间复杂度O(NlogN)
- 空间复杂度O(logN)
- 不稳定
// 问题1:给定一个数组arr和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。
vector<int> divide(vector<int>& arr, int num){
int i = -1;// 指向<=区的最后一个元素
int p = 0;
while(p < arr.size()){
// <=num, 和i的下一个数交换(插入<=区,<=区右扩)
if(arr[p] <= num){
swap(arr, ++i, p);
}
// >num,不做处理
p++;
}
return arr;
}
// 荷兰国旗问题:给定一个数组arr和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。
vector<int> dutchNationalFlag(vector<int> arr, int num){
int l = -1;
int b = arr.size();
int p = 0;
while(p < arr.size()){
//
if(arr[p] < num){
swap(arr, p++, ++l);
}
// =num,p右移。
else if(arr[p] == num){
p++;
}
// >num,和j的前一个数交换(>区左扩),p不动(交换后p还未被比较过)
else{
swap(arr, p, b);
}
}
return arr;
}
//快排1.0:最后一个数为num,以问题1为partition算法,不断递归。
//快排2.0:最后一个数为num,以荷兰国旗问题为partition算法,不断递归。
//快排3.0:随机一个数为num,以荷兰国旗问题为partition算法,不断递归。
vector<int> partition(vector<int>& nums, int l, int r){
int i = l - 1;
int j = r;// r位置是标杆
int p = l;
while(p < j){
if(nums[p] < nums[r]){
swap(nums, p++, ++i);
}else if(nums[p] == nums[r]){
p++;
}else{
swap(nums, p, --j);
}
}
// >区的左边界和标杆换位置
swap(nums, j, r);
// 返回=区的左右边界
vector<int> res {i+1, j};
return res;
}
vector<int> process(vector<int>& nums, int l, int r){
if(l < r){
// 挑一个随机数和r交换做标杆
swap(nums, round(1.0*rand()/RAND_MAX*(r-l)+l, r);
// p[0]是=区的左边界,p[1]是=区的右边界
vector<int> p = partition(nums, l, r);
// <区再做快排
process(nums, l, p[0]-1);
// >区再做快排
process(nums, p[1]+1, r);
}
return nums;
}
vector<int> quickSort(vector<int>& nums){
if(nums.size() < 2) return nums;
return process(nums, 0, nums.size()-1);
}
// 优化:样本数<60使用插入排序;其余使用快排。
- 时间复杂度O(NlogN)
- 空间复杂度O(1)
- 不稳定
- 优先队列结构就是堆结构。
// 完全二叉树中,(结点下标为i),i结点的父结点为(i-1)/2,左孩子2i+1,右孩子2i+2。
// 给了个大根堆,从index位置开始往上调整成大根堆。
// 不断跟父节点比较,比父节点大上浮。
// 时间复杂度O(logN)
void heapInsert(vector<int>& arr, int index){
// 比父亲小 或 我的父亲就是我自己 时跳出循环。
while(arr[index] > arr[(index-1)/2]){
swap(arr, index, (index-1)/2);
index = (index - 1)/2;
}
}
// 给了个大根堆,从index位置开始往下调整成大根堆。
// 不断跟子节点较大的比较,比子节点小下沉。
// 时间复杂度O(logN)
void heapify(vector<int>& arr, int index, int heapSize){
int lchild = index*2+1;
while(lchild < heapSize){
// 左右孩子进行比较
int bigOne = lchild+1 < heapSize && arr[lchild] < arr[lchild+1] ? lchild+1 : lchild;
// 左右孩子中较大的和父亲进行比较
bigOne = arr[bigOne] > arr[index] ? bigOne : index;
if(bigOne == index) break;
swap(arr, bigOne, index);
index = bigOne;
lchild = index * 2 + 1;
}
}
// 给了个大根堆,把index位置改成某个数num,要求调整成大根堆。
// num和arr[index]进行比较,如果numarr[index]往上调整。
// 堆排序
void heapSort(vector<int>& arr){
if(arr.size() < 2) return;
// 从0位置开始插入, 建立大根堆
// for(int i = 0; i < arr.size(); i++){
// heapInsert(arr, i);
// }
int heapSize = arr.size();
// 倒序遍历,向下调整大根堆
for(int i = arr.size()-1; i >= 0; i--){
heapify(arr, i, heapSize);
}
// 不断把最大的摘下来,放在最后
swap(arr, 0, --heapSize);
while(heapSize > 0){
heapify(arr, 0, heapSize);
swap(arr, 0, --heapSize);
}
}
// 已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法针对这个数据进行排序。
void sortedArrDistanceLessK(vector<int>& arr, int k){
priority_queue<int> heap;
int index = 0;// 指向数组遍历到哪
// 把数组前k个数放入堆内,调整堆。
// 求出数组长度和k中的最小值(防止k>>数组长度)
for(; index < min(arr.size() , k); index++){
heap.push(arr[index]);
}
int i = 0;// 指向结果放到哪
// 不断把第k+1个数放进堆里,调整堆,弹出堆顶
for(; index < arr.size(); i++, index++){
heap.push(arr[index]);
// 弹出堆顶,放在i位置上
arr[i] = heap.top();
heap.pop();
}
while(!heap.empty()){
arr[i++] = heap.top();
heap.pop();
}
}
非基于比较的排序
- 根据数据对象决定排序方法。
- 建立数组,下标即为数字,统计数字出现的频率。
- 不稳定
#include
const radix = 10;
int getDigit(int x, int d){
return ((x / pow(10, d-1))%10);
}
void radixSort(vector<int>& arr, int l, int r, int digit){
int i = j = 0;
vector<int> bucket(r-l+1);
for(int d = 1; d <= digit; d++){
// 原count数组,count[i]表示d位上,i出现的次数。
// 现count数组,count[i]等于count[i]前面的数字累加,表示d位上<=i的有几个(每个数字的片加起来);每个数该放在bucket数组下标=count[i]-1。
vector<int> count(radix);
// 统计出现的次数
for(i = l; i <= r; i++){
j = getDigit(arr[i], d);
count[j]++;
}
// 累加
for(i = 1; i < radix; i++){
count[i] += count[i-1];
}
// 从右往左遍历数组(保证先入先出)
for(i = r; i >= l; i--){
j = getDigit(arr[i], d);
bucket[count[j]-1] = arr[i];
count[j]--;
}
for(i = l, j = 0; i <= r; i++, j++){
arr[i] = bucket[j];
}
}
}
// 求最多有几位radix
int maxbits(vector<int>& arr){
int max = INT_MAX;
for(int i = 0; i < arr.size(); i ++){
max = max(max, arr[i]);
}
int res = 0;
while(max != 0){
res++;
max /= radix;
}
return res;
}
vector<int> radixSortMain(vector<int>& arr){
if(arr == null || arr.size() < 2) return arr;
radixSort(arr, 0, arr.size()-1, maxbits(arr));
return arr;
}
找某个数
- 有序数组,找某个数是否存在。
- 时间复杂度O(logN)
// 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
// leetcode-704
// 1、左闭右闭[left, right]
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size()-1;
int middle;
// 问题1:是<=还是<?
// 答:因为这里是左闭右闭,举特殊例子,当区间[1, 1]时,left=right是有意义的,仍要进行查找,所以是<=。
while(left <= right){
middle = left + ((right - left) >> 1);
if(nums[middle] > target){
// 更新右边界
// 问题2:是middle还是middle-1?
// 答:middle已经判断过了,所以是middle-1
right = middle - 1;
}else if(nums[middle] < target){
// 更新左边界
left = middle + 1;
}else{
return middle;
}
}
return -1;
}
// 2、左闭右开[left, right)
int search(vector<int>& nums, int target) {
int left = 0;
// 左闭右开,右边界不包含
int right = nums.size();
int middle;
// 当left=right,[1, 1)不合法,所以是<
while(left < right){
middle = l + (r - l) >> 1;
if(nums[middle] > target){
// 更新右边界,右开,将右边界定为middle
right = middle;
}else if(nums[middle] < target){
// 更新左边界,左闭,将左边界定为middle+1(middle已经判断过)
left = middle + 1;
}else{
return middle;
}
}
return -1;
}
找最左侧
- 有序数组,找>=或者>某个数最左侧的位置。
- 时间复杂度O(logN)
// 找lower_bound:找>=某个数的第一个位置
// 左闭右开
int lower_bound(vector<int>& nums, int target) {
int left = 0;
int right = nums.size();
int middle;
// left=right夹出唯一位置
while(left < right){
middle = left + ((right - left) >> 1);
if(nums[middle] >= target){
// 更新右边界:middle左边可能还有>=target的
right = middle;
}else{
// 更新左边界:>=target在middle右边
left = middle + 1;
}
}
// 返回夹出来的位置
return left;
}
// 找upper_bound:找>某个数的第一个位置
// 左闭右开
int upper_bound(vector<int>& nums, int target){
int left = 0;
int right = nums.size();
int middle;
while(left < right){
middle = left + ((right - left) >> 1);
if(nums[middle] > target){
// 更新右边界:middle左边可能还有>target的
right = middle;
}else{
// 更新左边界:>target在middle右边
left = middle + 1;
}
}
return left;
}
// leetcode-35(可能不存在)
// 1、暴力解
int searchInsert(vector<int>& nums, int target) {
for(int i = 0; i < nums.size(); i ++){
// 一旦发现>=目标值的就返回
if(nums[i] >= target){
return i;
}
}
// 否则返回数组长度
return nums.size();
}
// 2、二分查找(左闭右开)
int searchInsert(vector<int>& nums, int target){
int l = 0;
int r = nums.size();
int m;
while(l < r){
m = l + ((r-l) >> 1);
if(nums[m] >= target){
r = m;
}else{
l = m + 1;
}
}
// 判断target是否存在于数组中
/*
1、target存在 => 返回l
2、target不存在 => 比所有数大 => 返回nums.size()
=> 夹在中间 =>返回l
*/
// 判断l是否比所有数大
if(l == nums.size()) return nums.size();
return l;
}
// 找元素的第一个和最后一个位置
// leetcode-34
vector<int> searchRange(vector<int>& nums, int target) {
if(nums.empty()) return {-1, -1};
vector<int> res;
// 左闭右开
int l = 0;
int r = nums.size();
int m;
// 找<=target的右边界
while(l < r){
m = l + ((r-l)>>1);
if(nums[m] >= target){
r = m;
}else{
l = m + 1;
}
}
// 查找失败先返回
if(l == nums.size()) return {-1, -1};
res.push_back(l);
// 找>=target的右边界
// 从上一步的r开始找
l = r;
r = nums.size();
while(l < r){
m = l + ((r-l) >> 1);
if(nums[m] <= target){
l = m + 1;
}else{
r = m;
}
}
if(l-1 == nums.size()) return {-1, -1};
res.push_back(l-1);
if(res[1]>=res[0]){
return res;
}else{
return {-1, -1};
}
}
局部最小值
无序数组,相邻数一定不相等,求局部最小值。
- 时间复杂度O(n)
二分法的应用
// 算平方根
// leetcode-69
int mySqrt(int x) {
// 题意相当于找m^2最接近x的最大的数
// 特殊情况(0、1)返回
if(x <= 1) return x;
// 左闭右闭
int l = 1, r = x / 2 + 1, m;// 当x>1且为整数时,根号x在[1,x/2+1]区间内
while(l <= r){
m = l + ((r-l) >> 1);
if((long long)m*m < x){
l = m + 1;// 更新右边界
}else if((long long)m*m > x){
r = m - 1;// 更新左边界
}else{
return m;
}
}
// 如果不是通过m^2=x返回,则返回r
return r;
}
// 判断是否为完美平方根
// leetcode-367
bool isPerfectSquare(int num) {
int l = 1, r = num / 2 + 1, m;
while(l <= r){
m = l + ((r - l) >> 1);
if((long long)m*m < num){
l = m + 1;
}else if((long long)m*m > num){
r = m - 1;
}else{
return true;
}
}
return false;
}
// leetcode-26
// leetcode-283
// leetcode-844
bool backspaceCompare(string s, string t) {
int i = -1;// s有效序列
for(int k = 0; k < s.length(); k++){
if(s[k] == '#'){
if(i>-1){//有路可退,退退退!
i--;
}
}else{
s[++i] = s[k];//让k指向的元素纳入有效序列
}
}
int j = -1;// t有效序列
for(int p = 0; p < t.length(); p++){
if(t[p] == '#'){
if(j > -1){
j--;
}
}else{
t[++j] = t[p];
}
}
if(i == -1||j == -1){//最后剩余的是空格
return i == j;
}
// substr(开始位置,截取长度)
return s.substr(0, i+1) == t.substr(0,j+1);
}
// leetcode-977
// 方法1:双指针从中间开始移动(小的先插入)
vector<int> sortedSquares(vector<int>& nums) {
vector<int> res;
int i = 0, j = 0; // i负数列,j正数列
while(j<nums.size() && nums[j]<0){
j++;//1
}
i = j - 1;//0
while(i >= 0 && j < nums.size()){
if(abs(nums[i]) <= nums[j]){
res.push_back(nums[i]*nums[i]);
i--;
}else{
res.push_back(nums[j]*nums[j]);
j++;
}
}
while(i >= 0){
res.push_back(nums[i]*nums[i]);
i--;
}
while(j < nums.size()){
res.push_back(nums[j]*nums[j]);
j++;
}
return res;
}
// 方法2:双指针分别从两端开始移动(大的先插入)
vector<int> sortedSquares(vector<int>& nums) {
vector<int> res(nums.size());
int k = res.size()-1;
for(int i = 0, j = nums.size()-1; i <= j;){
if(nums[i]*nums[i] >= nums[j]*nums[j]){
res[k--] = nums[i]*nums[i];
i++;
}else{
res[k--] = nums[j]*nums[j];
j--;
}
}
return res;
}
// leetcode-209
int minSubArrayLen(int target, vector<int>& nums) {
int result = INT32_MAX;// 记录最小长度
int sum = 0;// 记录子序列和
int i = 0;// 起始位置
int subLength = 0;// 子序列长度
// 终止位置
for(int j = 0; j < nums.size(); j++){
// 累加
sum += nums[j];
while(sum >= target){
// 更新最小长度
subLength = j - i + 1;
result = result < subLength ? result : subLength;
// 把滑动窗口头摘下来,更新i
sum -= nums[i++];
}
}
return result == INT32_MAX ? 0 : result;
}
// leetcode-904
int totalFruit(vector<int>& fruits) {
int result;// 记录最大长度
unordered_map<int, int> m;// type, count
int i = 0;// 起始位置
int len = 0;// 记录子序列最大长度
// 终止位置
for(int j = 0; j < fruits.size(); j++){
// 累加
m[fruits[j]] ++;
len ++;
while(m.size()>2){
// 把头摘下来
m[fruits[i]] --;
if(m[fruits[i]] == 0){
m.erase(fruits[i]);
}
i++;
len--;
}
// 更新最大长度
result = result < len ? len : result;
}
return result;
}
// leetcode-76
struct ListNode{
int val;
ListNode* next;
// 构造函数
ListNode(int x): val(x), next(null) {}
}
// leetcode-203
// 1、分情况讨论
ListNode* removeElements(ListNode* head, int val) {
// 删除头节点
while(head != NULL && head->val == val){
ListNode* tmp = head;
head = head->next;
delete tmp;
}
// 删除非头节点
ListNode* curr = head;
// 注意可能存在删到最后空了
while(curr != NULL && curr->next != NULL){
if(curr->next->val == val){
// 删除curr->next
ListNode* tmp = curr->next;
curr->next = curr->next->next;
delete tmp;
}else{
curr = curr->next;
}
}
return head;
}
// 2、添加虚拟节点
ListNode* removeElements(ListNode* head, int val) {
// 设置一个虚拟头节点
ListNode* h = new ListNode(0);
// next指向链表头
h->next = head;
ListNode* curr = h;
while(curr->next != NULL){
if(curr->next->val == val){
ListNode* tmp = curr->next;
curr->next = curr->next->next;
delete tmp;
}else{
curr = curr->next;
}
}
// 删除虚拟头节点
head = h->next;
delete h;
return head;
}
// 反转单链表
// leetcode-206
// 1、指针法
ListNode* reverseList(ListNode* head) {
ListNode* prev = NULL;
ListNode* curr = head;
ListNode* next = head;
while(curr != NULL){
next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
return prev;
}
// 2、递归法
ListNode* reverseList(ListNode* head) {
// 递归的边界
if(head == NULL || head->next == NULL){
return head;
}
// last用来指向反转后的头节点
ListNode* last = reverseList(head->next);
// 反转
head->next->next = head;
head->next = NULL;
return last;
}
// 反转双链表
ListNode* reverse(ListNode* head){
ListNode* prev = NULL;
ListNode* curr = head;
while(curr != NULL){
curr->prev = curr->next;
curr->next = prev;
prev = curr;
curr = curr.prev;
}
return prev;
}
// 谁小谁移动,相同打印并移动,有一个越界停。
void printPublic(ListNode* head1, ListNode* head2){
ListNode* p1 = head1;
ListNode* p2 = head2;
while(p1 != NULL && p2 != NULL){
if(p1->val < p2->val){
p1 = p1->next;
}else if(p1->val > p2->val){
p2 = p2->next;
}else{
cout << p1->val << endl;
p1 = p1->next;
p2 = p2->next;
}
}
}
// leetcode-234
// 1、辅助栈
bool isPalindrome(ListNode* head) {
stack<int> s;
ListNode* p = head;
while(p!=NULL){
s.push(p->val);
p = p->next;
}
p = head;
while(p!=NULL){
if(s.top() != p->val){
return false;
}
s.pop();
p = p->next;
}
return true;
}
// 2、快慢指针
ListNode* reverse(ListNode* head){
ListNode* prev = NULL;
ListNode* curr = head;
ListNode* next = head;
while(curr!=NULL){
next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
return prev;
}
bool isPalindrome(ListNode* head) {
if(head == NULL || head->next == NULL){
return true;
}
ListNode* slow = head;
ListNode* fast = head;
while(fast.next!=NULL && fast->next->next!=NULL){
// 慢指针一个一个走,快指针走双倍
// 如果链表长度是奇数,最后slow走到中间,fast走到end
// 如果链表长度是偶数,最后slow走到中间后一个,fast走到end+1(null)
slow = slow->next;
fast = fast->next->next;
}
// 链表长度是奇数,slow还要往后走一个(正中间就不比较了)
if(fast!=NULL){
slow = slow->next;
}
// 分成左右两块开始遍历
ListNode* left = head;
ListNode* right = reverse(slow);
while(right!=NULL){
if(left->val != right->val){
return false;
}
left = left->next;
right = right->next;
}
return true;
}
// 定义六个指针变量sH/sT/eH/eT/bH/bT
// 1、分成<,=,>区版本
ListNode* partition(ListNode* head, int val){
ListNode* sH = NULL;
ListNode* sT = NULL;
ListNode* eH = NULL;
ListNode* eT = NULL;
ListNode* bH = NULL;
ListNode* bT = NULL;
ListNode* next = NULL;// 记录下一个节点
while(head != NULL){
next = head->next;
// 摘下头节点
head->next = null;
// 连接
if(head->val < val){
if(sH == NULL){
sH = head;
sT = head;
}else{
sT->next = head;
sT = head;
}
}else if(head->val == val){
if(eH == NULL){
eH = head;
eT = head;
}else{
eT->next = head;
eT = head;
}
}else{
if(bH == NULL){
bH = head;
bT = head;
}else{
bT->next = head;
bT = head;
}
}
head = next;
}
// s区非空,连接s区和e区
if(sT != NULL){
sT->next = eH;
// 判断e区是否为空,谁去连接b区
eT = eT == NULL ? sT : eT;
}
// e区非空,连接e区和b区
// 如果最后选择的去连接b区的不是空
if(eT != NULL){
eT->next = bH;
}
return sH != NULL ? sH : (eH != NULL ? eH : bH);
}
// 2、分成<,>=区版本
// leetcode-86
ListNode* partition(ListNode* head, int x) {
ListNode* sH = NULL;
ListNode* sT = NULL;
ListNode* eH = NULL;
ListNode* eT = NULL;
ListNode* next = NULL;
while(head != NULL){
next = head->next;
head->next = NULL;
if(head->val < x){
if(sH == NULL){
sH = head;
sT = head;
}else{
sT->next = head;
sT = head;
}
}else{
if(eH == NULL){
eH = head;
eT = head;
}else{
eT->next = head;
eT = head;
}
}
head = next;
}
if(sT != NULL){
sT->next = eH;
}
return sH != NULL ? sH : eH;
}
// leetcode-138
// 1、哈希表(需要额外空间)
Node* copyRandomList(Node* head) {
if(head == NULL) return head;
unordered_map<Node*, Node*> m;
Node* curr = head;
while(curr != NULL){
// key旧节点,value新节点
m[curr] = new Node(curr->val);
curr = curr->next;
}
curr = head;
while(curr != NULL){
// 遍历旧节点,设置新节点的next和random
m[curr]->next = m[curr->next];
m[curr]->random = m[curr->random];
curr = curr->next;
}
return m[head];
}
// 2、骚操作(不需要额外空间)
Node* copyRandomList(Node* head) {
if(head == NULL) return head;
Node* curr = head;
Node* next = NULL;
// 在老节点后面拷贝出一个新节点
while(curr != NULL){
next = curr->next;
curr->next = new Node(curr->val);
curr->next->next = next;
curr = next;
}
curr = head;
// 拷贝新节点的random
Node* curCopy = NULL;// 分离新旧节点要用,不可省
while(curr != NULL){
next = curr->next->next;
curCopy = curr->next;
// 修改新节点的random
curCopy->random = curr->random != NULL ? curr->random->next : NULL;
curr = next;
}
Node* res = head->next;
curr = head;
// 分离新旧节点
while(curr != NULL){
next = curr->next->next;
curCopy = curr->next;
// 修改旧节点的next
curr->next = next;
// 修改新节点的next
curCopy->next = next != NULL ? next->next : NULL;
curr = next;
}
return res;
}
// 求有环或无环链表相交节点
ListNode* getIntersectNode(ListNode* head1, ListNode* head2){
if(head1 == NULL || head2 == NULL){
return NULL;
}
ListNode* loop1 = getLoopNode(head1);
ListNode* loop2 = getLoopNode(head2);
if(loop1 == NULL && loop2 == NULL){
return noLoop(head1, head2);
}
if(loop1 != NULL && loop2 != NULL){
return bothLoop(head1, head2);
}
// 一个有环链表和一个无环链表是不可能相交的。
return NULL;
}
// (1)求环的入节点
ListNode* getLoopNode(ListNode* head){
if(head == NULL || head->next == NULL || head->next->next == NULL){
return NULL;
}
// 如果链表有环,快慢指针会相遇;且快慢指针在环中转的圈数不会大于两圈,因为快指针比慢指针快了一倍。
ListNode* slow = head->next;
ListNode* fast = head->next->next;
while(slow != fast){
if(fast->next == NULL || fast->next->next == NULL){
return NULL;
}
slow = slow->next;
fast = fast->next->next;
}
// 当快慢指针相遇,快指针回到头,快慢指针一起一步一步走,相遇时则为环的入节点。
fast = head;
while(slow != fast){
slow = slow->next;
fast = fast->next;
}
return fast;
}
// (2)求两个无环链表的相交节点
ListNode* noLoop(ListNode* head1, ListNode* head2){
if(head1 == NULL || head2 == NULL){
return NULL;
}
ListNode* cur1 = head1;
ListNode* cur2 = head2;
int n = 0;// 两个链表长度差值
while(cur1->next != NULL){
n++;
cur1 = cur1->next;
}
while(cur2->next != NULL){
n--;
cur2 = cur2->next;
}
// 走到结束不是同一个节点,则没有相交节点
if(cur1 != cur2){
return NULL;
}
// 让cur1指向长链表头
cur1 = n>0 ? head1 : head2;
cur2 = cur1 == head1 ? head2 : head1;
n = abs(n);
// 长链表走差值步数,走到和短链表长度相同处
while(n != 0){
n--;
cur1 = cur1->next;
}
// 求相交节点
while(cur1 != cur2){
cur1 = cur1->next;
cur2 = cur2->next;
}
return cur1;
}
// (3)求两个有环链表的相交节点
ListNode* bothLoop(ListNode* head1, ListNode* loop1, ListNode* head2, ListNode* loop2){
ListNode* cur1 = NULL;
ListNode* cur2 = NULL;
// 入环节点相同,化为求无环链表的相交节点
if(loop1 == loop2){
cur1 = head1;
cur2 = head2;
int n = 0;
while(cur1->next != NULL){
n++;
cur1 = cur1->next;
}
while(cur2->next != NULL){
n--;
cur2 = cur2->next;
}
cur1 = n>0 ? head1 : head2;
cur2 = cur1 == head1 ? head2 : head1;
n = abs(n);
while(n != 0){
n--;
cur1 = cur1->next;
}
while(cur1 != cur2){
cur1 = cur1->next;
cur2 = cur2->next;
}
return cur1;
}else{
// 入环节点不同
cur1 = loop1.next;
while(cur1 != loop1){
if(cur1 == loop2){
return loop1;
}
cur1 = cur1->next;
}
return NULL;
}
}
// leetcode-19
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* vhead = new ListNode(0, head);
ListNode* slow = vhead;
ListNode* fast = vhead;
// 快指针先走n步
while(n != 0 && fast != nullptr){
fast = fast->next;
n--;
}
// 快指针再提前走一步(目的:让慢指针指向被删除节点的前一个)
fast = fast->next;
while(fast != nullptr){
slow = slow->next;
fast = fast->next;
}
// 删除
slow->next = slow->next->next;
return vhead->next;
}
struct BTNode{
int val;
BTNode* left;
BTNode* right;
BTNode(int data): val(data){}
}
// 递归遍历(每个节点遍历3遍)
void recursion(BTNode* head){
if(!head) return;
// 1
// 1
recursion(head->left);
// 2
// 2
recursion(head->right);
// 3
// 3
}
// 递归版
// 先序遍历(在1的时候打印)
void preOrderRecur(BTNode* head){
if(!head) return;
cout << head->val << endl;
preOrderRecur(head->left);
preOrderRecur(head->right);
}
// 中序遍历(在2的时候打印)
void inOrderRecur(BTNode* head){
if(!head) return;
inOrderRecur(head->left);
cout << head->val << endl;
inOrderRecur(head->right);
}
// 后序遍历(在3的时候打印)
void posOrderRecur(BTNode* head){
if(!head) return;
posOrderRecur(head->left);
posOrderRecur(head->right);
cout << head->val << endl;
}
// 非递归版
// 先序遍历:根左右
void preOrdeUnrecur(BTNode* head){
if(head){
stack<BTNode*> s;
s.push(head);
while(!s.empty()){
// 弹出节点,处理
head = s.top();
cout << head->val << endl;
s.pop();
// 右孩子进栈
if(head->right){
s.push(head->right);
}
// 左孩子进栈
if(head->left){
s.push(head->left);
}
}
}
}
// 中序遍历:左根右
void inOrderUnrecur(BTNode* head){
if(head){
stack<BTNode*> s;
while(!s.empty() || head != nullptr){
// 根节点的整个左边界进栈
if(head){
s.push(head);
head = head->left;
}else{
// 弹出栈顶,处理
head = s.top();
cout << head->val << endl;
s.pop();
// 走右树
head = head->right;
}
}
}
}
// 后序遍历:左右根(利用收集栈)
void posOrderUnrecur(BTNode* head){
if(head){
stack<BTNode*> s1;
stack<BTNode*> s2;// 收集栈
s1.push(head);
while(!s1.empty()){
// 弹出节点,压入收集栈
head = s1.top();
s2.push(head);
s1.pop();
// 左孩子进栈
if(head->left){
s1.push(head->left);
}
// 右孩子进栈
if(head->right){
s1.push(head->right);
}
}
while(!s2.empty()){
cout << s1.top()->val << endl;
s1.pop();
}
}
}
// 层序遍历(广度优先):用队列
void floorTraversing(BTNode* head){
if(head){
queue<BTNode*> q;
q.push(head);
while(!q.empty()){
// 队头出队
BTNode* cur = q.front();
cout << cur->val << endl;
q.pop();
// 左孩子进队
if(cur->left){
q.push(cur->left);
}
// 右孩子进队
if(cur->right){
q.push(cur->right);
}
}
}
}
// 求二叉树的最大宽度:哈希表
int maxWidth(BTNode* head){
if(head){
queue<BTNode*> q;
unordered_map<BTNode*, int> levelMap;
q.push(head);
levelMap[head] = 1;
int curLevel = 1;// 当前层数
int curLevelNodes = 0;// 当层节点数
int max = 0;// 最大宽度
while(!q.empty()){
BTNode* cur = q.front();
q.pop();
int curNodeLevel = levelMap[cur];
if(curNodeLevel == curLevel){
curLevelNodes ++;
}else{// 该更新了
max = max(max, curLevelNodes);
curLevel ++;
curLevelNodes = 1;
}
if(cur->left){
q.push(cur->left);
levelMap[cur->left] = curNodeLevel+1;
}
if(cur->right){
q.push(cur->right);
levelMap[cur->right] = curNodeLevel+1;
}
}
// 更新最后一层
max = max(max, curLevelNodes);
return max;
}
}
// 求二叉树的最大宽度(这里的宽度包括nullptr):编号
// leetcode-662
int widthOfBinaryTree(TreeNode* root) {
queue<pair<TreeNode*, long long> > q;
q.emplace(root, 0);
int res = 0;
while(!q.empty()){
// 队头的编号(最左边节点的编号)
long long offset = q.front().second;
// 基于队头和队尾获得当前层的宽度
res = max(res, (int)(q.back().second - offset + 1));
// 遍历当前层
int n = q.size();
for(int i = 0; i < n; i++){
// 当层每个节点依次出队列
auto [cur, curVal] = q.front();
q.pop();
// 编号缩小
curVal -= offset;
if(cur->left){
// 有左孩子,设置左孩子的值,进队
q.emplace(cur->left, curVal * 2);
}
if(cur->right){
// 有右孩子,设置右孩子的值,进队
q.emplace(cur->right, curVal * 2 + 1);
}
}
}
return res;
}
// 左根右
TreeNode* getSuccessorNode(TreeNode* node){
if(node == nullptr) return node;
// 如果node有右孩子,后继节点为右子树上最左边的节点
if(node->left != nullptr){
return getLeftMost(node->right);
}else{
// 如果node无右孩子
TreeNode* parent = node->parent;
// node为parent的右孩子,后继节点为祖先中第一个为它父亲的左孩子的节点
while(parent != nullptr && parent->left != node){
node = parent;
parent = node->parent;
}
return parent;
// 还有一种情况,如果找不到,则说明node为最右节点,没有后继节点
}
}
TreeNode* getLeftMost(TreeNode* node){
if(node == nullptr){
return node;
}
while(node->left != nullptr){
node = node->left;
}
return node;
}
// 按先序
// leetcode-297
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
if(root == nullptr) return "#_";// 加_是因为val可能是多位数。
// int转string
string res = to_string(root->val) + '_';
res += serialize(root->left);
res += serialize(root->right);
return res;
}
// Decodes your encoded data to tree.
queue<string> split(string& str){
queue<string> res;
string s;
for(auto c : str){
if(c == '_'){
res.emplace(s);
s.clear();
}else{
s += c;
}
}
return res;
}
TreeNode* process(queue<string>& q){
if(q.empty()) return nullptr;
string s = q.front();
q.pop();
if(s == "#") return nullptr;
// string转int
TreeNode* cur = new TreeNode(stoi(s));
cur->left = process(q);
cur->right = process(q);
return cur;
}
TreeNode* deserialize(string data) {
queue<string> q = split(data);
return process(q);
}
// 二叉树中序遍历就是纸条从左到右的折痕顺序,根节点是凹,左孩子是凹,右孩子是凸。
void printAllFolds(int n){
printProcess(1, n, true);
}
// i节点层数;总层数;是否为凹
void printProcess(int i, int n, bool down){
if(i > n) return;
printProcess(i+1, n, true);
cout << down?"凹":"凸" << endl;
printProcess(i+1, n, false);
}
// 判断搜索二叉树:中序遍历依次升序就是搜索二叉树
// leetcode-98
// 1、递归
long long preVal = LONG_MIN;
bool isValidBST(TreeNode* root) {
if(root == nullptr) return true;
if(!isValidBST(root->left)){
return false;
}
if(root->val > preVal){
preVal = root->val;
}else{
return false;
}
return isValidBST(root->right);
}
// 2、中序遍历
// 递归版
void process(TreeNode* root, vector<int>& list){// 注意加引用&
if(root == nullptr) return;
process(root->left, list);
list.emplace_back(root->val);
process(root->right, list);
}
bool isValidBST(TreeNode* root) {
vector<int> list;
process(root, list);
for(int i = 0; i < list.size()-1; i++){
if(list[i] >= list[i+1]){
return false;
}
}
return true;
}
// 非递归版
bool isValidBST(TreeNode* root) {
long long preVal = LONG_MIN;
stack<TreeNode*> s;
while(!s.empty() || root != nullptr){
if(root){
s.push(root);
root = root->left;
}else{
root = s.top();
s.pop();
if(root->val <= preVal){
return false;
}else{
preVal = root->val;
}
root = root->right;
}
}
return true;
}
// 判断完全二叉树:层次遍历
bool isCBT(TreeNode* root){
if(root == nullptr) return true;
queue<TreeNode*> q;
bool leaf = false;// 是否遇到第一个左右孩子不双全的节点(开启后,如果接下来遇到的全是叶节点,则为完全二叉树)
TreeNode* l = nullptr;
TreeNode* r = nullptr;
q.push(root);
while(!q.empty()){
root = q.top();
q.pop();
l = root->left;
r = root->right;
// 如果只有右孩 或者 leaf开启但不是叶节点
if((l == nullptr && r != nullptr)
|| (leaf && (l != nullptr || r != nullptr))
){
return false;
}
if(l){
q.push(l);
}
if(r){
q.push(r);
}
// 遇到左右孩子不双全,开启leaf
if(l == nullptr || r == nullptr){
leaf = true;
}
}
return true;
}
// 判断满二叉树:用树形dp套路
// 判断平衡二叉树:树形dp套路
// leetcode-110
int process(TreeNode* root){
if(root == nullptr) return 0;
int lh = process(root->left);
int rh = process(root->right);
// 如果这颗子树是平衡二叉树
if(lh >= 0
&& rh >= 0
&& abs(lh-rh) < 2
){
return max(lh, rh) + 1;
}
return -1;
}
bool isBalanced(TreeNode* root){
// >=0返回高度,是平衡二叉树;<0不是平衡二叉树
return process(root) >= 0;
}
// 给定两个二叉树的节点,找到他们的最低公共祖先节点。
// leetcode-236
// 1、哈希表(超时了)
void process(TreeNode* root, unordered_map<TreeNode*, TreeNode*>& fatherMap){
if(root == nullptr) return;
if(root->left){
fatherMap.emplace(root->left, root);
process(root->left, fatherMap);
}
if(root->right){
fatherMap.emplace(root->right, root);
process(root->right, fatherMap);
}
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q){
// 构造fatherMap
unordered_map<TreeNode*, TreeNode*> fatherMap;
fatherMap.emplace(root, root);
process(root, fatherMap);
// 用set记录p往上窜到根节点的路
unordered_set<TreeNode*> st;
TreeNode* cur = p;
// 当自己的爹=自己(根节点)跳出
while(cur != fatherMap[cur]){
st.emplace(cur);
cur = fatherMap[cur];
}
st.emplace(root);
// q向上遍历,检查set是否存在
cur = q;
while(st.find(fatherMap[cur]) != st.end()){
cur = fatherMap[cur];
}
return cur;
}
// 2、递归:想不到吧哈哈
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q){
// base case
if(root == nullptr || root == p || root == q){
return root;
}
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
// 左右lca都不为空(p、q分别在左右子树)
if(left != nullptr && right != nullptr){
return root;
}
// 左右lca存在空(p或q在一边子树)、都为空(p、q不在当前左右子树)
return left != nullptr ? left : right;
}