leetcode学习记录_数组

人决定分类型来做,一开始是简单的数组以及其他,后期在涉及其他数据结构


数组

如果有说明数组内的数据均为0~n-1,那我们就可以把数组和下标联合起来,进行思考

例如:
剑指 Offer 03. 数组中重复的数字这道题里面的原地置换法
这题的条件就是从数组中找重复的数字,且数组内的数据均为0~n-1
三种解法:
第一种最容易想到的,暴力解法:
原理就是先利用sort对数组进行排序,然后重复的数字必然会挨在一起,以此为依据来查找

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        sort(nums.brgin() , nums.end());
        for(int idx = 0;idx < nums.size()-1;idx++)
        {
        	if(nums[idx] == nums[idx+1])
        	return nums[idx];
        }
        return -1;
    }
};

第二种,以当前数组的数据建立哈希表,利用哈希表的高查找效率

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        unordered_map <int,int> map; 
        for(int num : nums)
        {
            if(++map[num] > 1)    return num;
        }   
        return -1;
    }
};

第三种,巧妙的原地置换法

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        int buf = 0;
        for(int idx = 0;idx<nums.size();idx++)
        {
        	while(nums[idx] != idx)
        	{
        		if(nums[idx] == nums[nums[idx]])
        		return nums[idx];
				buf = nums[idx];//交换位置
				nums[idx] = nums[nums[idx]];
				nums[nums[idx]] = nums[idx];
        	}
        }
        return -1;
    }
};

又例如
剑指 Offer 53 - II. 0~n-1中缺失的数字
这题的关键字是有序数组+查找,依照大佬们的思路,优先考虑二分查找
我们设置左、右、中间三个指针,又因为数组中每个数字都在范围0~n-1之内且只缺少了一个,所以也可以把下标和数据联系起来

class Solution {
public:
    int missingNumber(vector<int>& nums) {
		int L = 0, R = nums.size();
		while(L < R)
		{
			int mid  = L + ((R - L)>>1);//使用右移运算符比/2快
//如果中间指针的数据于下标对应,就表示缺的数字在右半边,
//于是我们把L往右挪,反之就把R往左挪			
			if(nums[mid] == mid) 
//为什么L要+1?因为我们要把L移动到到缺失的数字对应的下标上,这就意味着
//L的位置是不可能nums[mid] == mid的,所以直接跳过mid,到下一位上去;
			L = mid + 1;
			else 
			R = mid;
		}
		return L;
    }
};

另一个思路,并且适用于无序的情况
利用异或的性质
仔细观察,我们就能发现却一个数字的有序数组的共同点

在这里插入图片描述
除了一个“8”和缺少了数据的下标外,其他的数据和下标都是一一对应的,由此我们可以把所有的下标和数据进行异或运算
进行运算后,我们就只剩下想要得数字与那个“8”,而这个“8”,其实就是数组长度,因为数据和下标都是从0开始且有序,所以只要缺少一个,最后的数据必然是“8” ,我们只要再^nums.size(),就可以把多余的“8”去掉;

int missingNumber(vector<int>& nums)
{   
	int size = nums.size(),
	int data = 0;
	for (int idx = 0; idx < size; idx++) 
		data ^= nums[idx] ^ idx;
	return data ^ size;
}

此方法思路简单,易理解,但是时间复杂度是O(N),没有利用好有序这个条件

这里还有一题可以用到异或的题目:
剑指 Offer 56 - I. 数组中数字出现的次数
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
因为空间复杂度要求是O(1),所以用不了哈希表了,可以考虑以下异或
如果只有一个数字的话,直接全异或就行了,这里有两个数字,我们异或的话,最后会得到num1^num2,
咋把他们分开呢?直接分开或许是做不到,但是我们可以把数组分组,一边包含num1于其他成对的数字,另一边是num2与剩下的成对的数字
原理:0 ^ 0 = 0 、1 ^ 1 = 0 、 但是0 ^ 1 = 1,于是我们找到num1^num2里为1的任意一位,这一位就就是区分开num1 和 num2 重要线索,其他的元素因为是成对的,所以用这一位就能把他们分到同一组
代码:

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
    	int res = 0 , num1 = 0 , num2 = 0;
		for(int n : nums)//求得num1 ^ num2的值
		{
			res ^= n;
		}
		int idx = 1;
		while(!(idx & res))
		{
			idx = idx << 1;//求得res里为1的最低位
		}
		for(int n : nums)
		{
			if((n&idx) == 1)//分组并分别异或
			num1 ^= n;
			else
			num2 ^= n;
		}
		return vector<int> {num1,num2};//返回数组
    }
};


利用改进的双指针法实现有序数组中删除重复项这类题目的通解
80. 删除有序数组中的重复项 II
这题要求删除重复数据,使得一个重复的数据最多出现两次,因此数组长度<=2时都是特殊情况

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
    	int i = 0;//定义指针1
		for(num : nums)//这里的num就相当于第二个指针
		{//将此处两个2改为其他数字即可应对不同题目
			if(i<2 || nums[i-2] != num)
			{
//i<2是特殊情况,而整个判断条件可以理解为,遇到重复值的时候,
//i就不往前走,因为i是代表最后的数组长度,重复的数据是不能被算入长度内的
//而覆盖重复数据的操作就通过num来完成,当num到达不重复的数据的时候
//就用num的数据把i所在的数据给覆盖掉,然后长度加1,也就是i++
//这样就保证了在i的范围内,数据都是满足要求的,

				nums[i++] = num;
			}
		}
		return i;
    }
};

26. 删除有序数组中的重复项
同上的通用解法:

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
		int idx = 0;
		for(int num : nums)
		{
			if(idx<1 || nums[idx-1] != num)
			nums[idx++] = num;
		}
		return idx;
    }
};

27. 移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

来源:力扣(LeetCode)
思路:
原地删除,双指针就完事了

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
		int res = 0;
		for(int num : nums)
		{
//只有当前值不时要删除的值时,才进行替换,如果是要删除的值,
//就跳过,啥也不做,等到再次遇到正常值时,再把正常值替换回去,
//为此,res不能乱走,只有确定了当前值正常的时候才能往前走		
			if(num != val)
			{			
				nums[res] = num;
				res++;
			}
		}
		return res;
    }
};


有序旋转数组的最小值(无重复值)
153. 寻找旋转排序数组中的最小值
也是典型的二分法

class Solution {
public:
    int findMin(vector<int>& nums) {
		int N = nums.size();
		int L = 0, R = N ;
		while(L < R)
		{
			int mid = L + ((R - L)>>1);
			if(nums[mid]>=nums[0])
			L = mid + 1;
			else 
			R = mid;
		}
		if(L >= N) return nums[0]
		return nums[L] < nums[0] ? nums[L] : nums[0];
	}
};

有重复值
154. 寻找旋转排序数组中的最小值 II
有重复值的题目其实只多了一个while语句,这一步是恢复数组二段性得重要步骤

class Solution {
public:
    int findMin(vector<int>& nums) {
		int N = nums.size();
		int L = 0 , R = N - 1;
		while(L < R && nums[0] == nums[R])//恢复二段性
		{
			R--;
		}
		R++;//左闭右开
		while(L < R)
		{
			int mid = L + ((R - L)>>1);
			if(nums[mid]>=nums[0])
			L = mid + 1;
			else
			R = mid;
		}
		if(L >= N) return nums[0]
		return nums[L] < nums[0] ? nums[L] : nums[0];
	}
};


二维数组

剑指 Offer 04. 二维数组中的查找
这一题是一个上下,左右都有序(递增)的二维数组,要判断输入参数target是否在数组内,因为是有序的,所以我把它当成坐标轴来看待,从左下或右上去逼近于参数最相近的范围
在这里插入图片描述

class Solution {
public:
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
		int x = 0, y = matrix.size()-1;//我选择从左下角开始
//设置循环条件,当索引越界时就说明找不到该值,只能返回false了
		while(y >= 0 && x < matrix[0].size() )
		{
			if(matrix[y][x] > target) y--;
			else if(matrix[y][x] < target) x++;
			else return true;
		}
		return false;
    }
};


剑指 Offer 29. 顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
在这里插入图片描述

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        int m = matrix.size();
        if(m == 0) return  {};
//这里的n一定要在判断之后定义,不然如果是空数组的话就会出现无效指针,
//导致程序崩溃        
        int n = matrix[0].size();
        vector <int> res;
        res.reserve(m*n);
        int y = 0, x = 0, yup  = 0,ydown = m, xleft = -1, xright = n;
        int state = 1;
        while(1)
        {
            switch(state)
            {
                case 1:
                    while(x<xright)
                    {
                        res.emplace_back(matrix[y][x]);
                        x++;
                    }
                    x--;
                    y++;
                    xright--;
                    state = 2;
                    break;
                case 2:
                    while(y<ydown)
                    {
                        res.emplace_back(matrix[y][x]);
                        y++;
                    }
                    y--;
                    x--;
                    ydown--;
                    state = 3;
                    break;
                case 3:
                    while(x>xleft)
                    {
                        res.emplace_back(matrix[y][x]);
                        x--;
                    }
                    x++;
                    y--;
                    xleft++;
                    state = 4;
                    break;
                case 4:
                    while(y>yup)
                    {
                        res.emplace_back(matrix[y][x]);
                        y--;
                    }
                    y++;
                    x++;
                    yup++;
                    state = 1;
                    break;
                default:
                    break;
            }
            if((state == 1 || state == 3) && yup >= ydown) break;
            if((state == 2 || state == 4) && xleft >= xright) break;
        }
        return res;
    }
};

在这里插入图片描述
还有一题可以用这个套路:

59. 螺旋矩阵 II

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n);
        for(vector<int>& temp : res)
        {
            temp.resize(n);
        }
        int xright = n, xleft = -1, ydown = n, yup = 0;
        int y = 0, x = 0;
        int state = 1, num = 1;
        while(num<=(n*n))
        {
            switch(state)
            {
                case 1:
                while(x<xright)
                {
                    res[y][x] = num;
                    x++;num++;
                }
                ++y;--x;--xright;state = 2;
                break;
                case 2:
                while(y<ydown)
                {
                    res[y][x] = num;
                    y++;num++;
                }
                --y;--x;--ydown;state = 3;
                break;
                case 3:
                while(x>xleft)
                {
                    res[y][x] = num;
                    x--;num++;
                }
                --y;++x;++xleft;state = 4;                
                break;
                case 4:
                while(y > yup)
                {
                    res[y][x] = num;
                    y--;num++;
                }
                ++y;++x;++yup;state = 1;                
                break;
                default : break;
            }
        }
        return res;
    }
};


1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。
示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

来源:力扣(LeetCode)

思路:
遍历数组的同时把元素放入哈希表,一旦查到 target - nums[ i ]存在哈希表里,就返回这两个数字,遍历完了都没找到的话,就说明不存在这样的一对数字

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
		unordered_map<int ,int>map;
		for(int i =0;i<nums.size();i++)
		{//判断如果两个数字都在哈希表里,就直接返回下标,
		//下标储存在map里的value里
			if(map.find(target-nums[i])!=map.end())
			return vector<int>{i,map[target-nums[i]]}//返回
			map[nums[i]] = i;//key是数字,value是下标;
		}
		return {};
    }
};


4. 寻找两个正序数组的中位数

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

示例 1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
来源:力扣(LeetCode)

第一眼看见题目,俩有序数组,还中位数?归并排序然后直接中间位置走起,于是刷刷写下如下代码

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int n1 = nums1.size() , n2 = nums2.size();
        vector<int> buf(n1+n2,0);
        int p1 = 0 , p2 = 0;
        double res;
        for(int i = 0;i<n1+n2;i++)
        {
            int num1 = p1<n1?nums1[p1] : INT_MAX;
            int num2 = p2<n2?nums2[p2] : INT_MAX;
            if(num1 < num2)
            {
                buf[i] = num1;p1++;
            }
            else
            {
                buf[i] = num2;p2++;
            }     
        }
        if((n1+n2)%2 == 0)  
        res = ((double)buf[((n1+n2)/2)-1] + (double)buf[(n1+n2)/2])/2;
        else res = (double)buf[(n1+n2)/2];
        return res;
    }
};

写完调试完,提交,好像还不错😘
在这里插入图片描述

然后我才发现有进阶要求:
进阶:你能设计一个时间复杂度为 O(log (m+n)) 的算法解决此问题吗?
呃呃,有点难😩,待续…


剑指 Offer 51. 数组中的逆序对
第一眼,没啥想法,暴力法试试吧,然后:😢
在这里插入图片描述
暂时没啥头绪,先搁置吧…😥

时隔不知道多少天,终于搞懂了,这一题可以利用归并排序的特点:分而治之

写出归并排序的模板,再添加一行代码即可
模板👉在这

思路:

其实也就只需解释一下那行代码而已,不过前提是需要理解归并排序

为什么在nums[left1] > nums[left2] 时?为什么是mid-left1+1?

nums[left1] > nums[left2] 时代表左半部分当前遍历到的值比右半部分当前遍历到的值
于是!!! 此时nums[left1] 与 nums[left2]就组成了一组逆序对,
但是!!! 别忘了,合并的两个子数组又是有序的!!,所以nums[left1]到nums[mid]之间的所有数都比nums[left2]大,他们都能组成逆序对!!
mid-left1+1就是这个原理啦

最后因为归并排序的原理,从下往上分割合并,所以在用上述方法判断逆序对时,因为两个子数组里的元素相对位置不会错,所以是正确的!而且也不会漏判

代码:

class Solution {
    int count;//计数值
    void Merge(vector<int>& nums, int L,int mid, int R)
    {
        if(L > R)return;
        vector<int>temp(R-L+1);
        int left1 = L, left2 = mid+1, idx = 0;
        while(left1<=mid && left2<=R)
        {
            if(nums[left1]<=nums[left2])
            {
                temp[idx++] = nums[left1++];
            }
            else
            {
                count += mid-left1+1;//只需添加这一行即可
                temp[idx++] = nums[left2++];
            }
        }
        while(left1<=mid)   temp[idx++] = nums[left1++];;
        while(left2<=R)     temp[idx++] = nums[left2++];;
        for(int i = 0;i<temp.size();++i)
        nums[L+i] = temp[i];
    }
    void MSort(vector<int>& nums, int L, int R)
    {
        if(L >= R) return;
        int mid = L+((R-L)>>1);
        MSort(nums, L, mid);
        MSort(nums, mid+1, R);
        Merge(nums, L, mid, R);
    }
public:
    int reversePairs(vector<int>& nums) {
        count = 0;
        MSort(nums, 0, nums.size()-1);
        return count;
    }
};


118. 杨辉三角

很简单的一题,注意一下二维数组的初始化就行了

class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<vector<int>> res;
		res.resize(numRows);
		for(int i = 0;i<numRows;++i)//分别给每一行赋值
		{
			res[i].resize(i+1);//初始化各行的size
//给首位和末位填上1,如果对首位和末位用下面的公式的话就会越界,
//无奈只能提前初始化			
			res[i][0] = res[i][i] = 1;
			for(int j = 1;j<i;++j)
			{
				res[i][j] = res[i-1][j-1] + res[i-1][j];
			}
		}
		return res;
    }
};


48. 旋转图像

给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
来源:力扣(LeetCode)
在这里插入图片描述
先来个最简单的暴力解法,只是一种思路,并不满足题目要求

在这里插入图片描述

class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        vector<vector<int>> res = matrix;
        int row = matrix.size();
        for(int y = 0;y<row;++y)
        {
            for(int x = 0;x<row;++x)
            {
                res[x][row-y-1] = matrix[y][x];
            }
        }
        matrix = res;
    }
};

翻转法:
先上下翻转,再沿对角线翻转
在这里插入图片描述

class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int row = matrix.size();
        for(int i = 0;i<row/2;++i)
        {//上下翻转
            for(int j = 0;j<row;++j)
            swap(matrix[i][j],matrix[row-i-1][j]);
        }
        for(int i = 0;i<row;++i)
        {//对角线翻转
            for(int j = 0;j<i;++j)
            swap(matrix[i][j],matrix[j][i]);
        }
    }
};

对角线翻转的思路:

在这里插入图片描述
数字代表代码中翻转的顺序


31. 下一个排列
实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须 原地 修改,只允许使用额外常数空间。
示例 1:
输入:nums = [1,2,3]
输出:[1,3,2]
来源:力扣(LeetCode)

这其实就是字典的排序方法,假如是{ 1 , 2 , 3}
按照升序排列就如下

1 2 3
1 3 2
2 1 3 
2 3 1 
3 1 2 
3 2 1

思路:要求的不只是比当前的大的排列,而是要求下一个排列,就是要在大的排列里面找到最小的那一个,根据排序的规则,就是尽量不要改变前面,尽量改变最后面的顺序,这样才能最小增大
那么怎么找到该改哪个呢?
看图,黑色是原序列,红色是对应的下一个排列,我们可以发现,当最后n位是降序的话,根本无法在这n位里改变顺序,不管怎么排,都会变小,因为降序,最大的数字都在最前位了,只能往前找一个不满足降序的数字x,它就是突破口,因为它破坏了降序,所以后面的n位里肯定有比它大的,我们要做的就是找到比x大的值里面最小的那个,交换他们两个即可
在这里插入图片描述
然后重点来了:
我们前面的的事是为了什么,是为了能够在最小的限度内增大吧?而经过交换后,x的位置已经变大了,这就意味这不管我们怎么改后面的排序,新的排序都已经比原排列大了,于是为了秉持最小增大,我们把x后面的数字按增序重新排列,这样即可在已经大于原排列的情况下得到最小的那个排列

代码:

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int  N = nums.size();
        int i = N-1, j = N-1;
        while(i>0 && nums[i] <= nums[i-1]) --i;--i;//找到x,把i移动到x的下标处,
        //当然最后如果不想多减一次,那就略微修改下面的代码即可
        if(i >= 0)//这一步别忘了哦,假如原序列是最大排列(整体降序),这时候i为-1,
        //如果继续执行交换,就越界了,
        //当最大排列值,直接反转(或排序整个数组)即可
        {
            while(j>=0 && nums[j] <= nums[i]) --j;//找到比x大的最小值;
        swap(nums[j],nums[i]);//交换
        }
        sort(nums.begin()+i+1,nums.end());//排序
    }
};

这一题也可以用反转代替排序,因为反转的复杂度为O(n),比排序的O(n log n)要好,这里我偷懒就不写了


189. 旋转数组

给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。

进阶:

尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗?

示例 1:

输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]

来源:力扣(LeetCode)

方法一:
额外数组,很明显不满足进阶要求,但不失为一种解法
代码:

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        int size = nums.size();
        int L = 0, R = k%size;
        if(R == 0)return ;
        vector<int>buf = nums;
        for(int i = 0;i<size;++i)
        {
            buf[R++] = nums[L++];
            if(R == size)R = 0;
            if(L == size)L = 0;
        }
        nums = buf;
        return ;
    }
};

方法二:
翻转数组法,没想到旋转和翻转这么配啊,上面有一题二维数组的旋转,也是用翻转实现的
在这里插入图片描述
先全局翻转一次,然后吧翻转过得数组在分两部分翻转,分别是前k个,和剩余部分

代码: 极简三行… 实在不行就自己写一个翻转得程序,也不难…
因为C++得reverse是迭代器+开区间的,所以直接k%size就行了,不用管什么+1,-1等区间问题

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        reverse(nums.begin(),nums.end());
        reverse(nums.begin(),nums.begin()+k%nums.size());
        reverse(nums.begin()+k%nums.size(),nums.end());
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

timathy33

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值