剑offer - 2 -数据结构(栈/队列/矩阵/字符串/数组...)

17. 电话号码的字母组合

在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        
        vector<string> list={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
        queue<string> q;
        vector<string> v;
        if(digits.length() == 0) return v;
        string s = list[digits[0]-'0'];
        for(int j=0;j<s.length();j++){
            string str = s.substr(j,1);
            q.push(str);
        }
        int epoc1 = 0;
        for(int i=1;i<digits.length();i++){
            cout<<digits[i]<<" "<<s<<endl;             
            int size = q.size(); //用size控制每一个数字对应的一层字母
            if(epoc1 == 0) epoc1 = 1;
            for(int t = 0;t < size;t++){ //外层是队列遍历
                string tmp = q.front();q.pop();
                if(!epoc1) v.push_back(tmp);
                s = list[digits[i]-'0'];
                for(int j=0;j < s.length();j++){ //内层是对这一层字母的遍历
                    cout<<s[j]<<endl;
                    q.push(tmp + s[j]);
                }
            }
        }
        
        while(!q.empty()){
            v.push_back(q.front());q.pop();
        }
        return v;
    }
};

旋转数组让用三种方法

在这里插入图片描述
方法1:就正常
方法二:
在这里插入图片描述

方法三:如下:

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        int len = nums.size();
        int Gcd = gcd(k,len); //此次旋转只有Gcd个起点
        k = k % len; //缩小k;
        for(int start=0;start<Gcd;start++){
            int current = start;
            int prev_value = nums[start];
            do{
                int next = (current + k) % len;
                swap(nums[next],prev_value);
                current = next;
            }while(start != current);
        }
    }
};
TOP4. 寻找两个正序数组的中位数

之前学过的马车法
在这里插入图片描述


#include <stdio.h>
#include <vector>
using namespace std;

#define max(a,b) (((a) > (b)) ? (a) : (b))
#define min(a,b) (((a) < (b)) ? (a) : (b))

class Solution {
public:
	double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
		int n = nums1.size();
		int m = nums2.size();

		if (n > m)  //保证数组1一定最短
		{
			return findMedianSortedArrays(nums2, nums1);
		}

		// Ci 为第i个数组的割,比如C1为2时表示第1个数组只有2个元素。
		//LMaxi为第i个数组割后的左元素。RMini为第i个数组割后的右元素。
		int LMax1, LMax2, RMin1, RMin2, c1, c2, lo = 0, hi = 2 * n;  
		//我们目前是虚拟加了'#'所以数组1是2*n长度

		while (lo <= hi)   //二分
		{
			c1 = (lo + hi) / 2;  //c1是二分的结果
			c2 = m + n - c1;

			LMax1 = (c1 == 0) ? INT_MIN : nums1[(c1 - 1) / 2];
			RMin1 = (c1 == 2 * n) ? INT_MAX : nums1[c1 / 2];
			LMax2 = (c2 == 0) ? INT_MIN : nums2[(c2 - 1) / 2];
			RMin2 = (c2 == 2 * m) ? INT_MAX : nums2[c2 / 2];

			if (LMax1 > RMin2)
				hi = c1 - 1;
			else if (LMax2 > RMin1)
				lo = c1 + 1;
			else
				break;
		}
		return (max(LMax1, LMax2) + min(RMin1, RMin2)) / 2.0;
	}
};


int main(int argc, char *argv[])
{
	vector<int> nums1 = { 2,3, 5 };
	vector<int> nums2 = { 1,4,7, 9 };
	
	Solution solution;
	double ret = solution.findMedianSortedArrays(nums1, nums2);
	return 0;
}

84. 柱状图中最大的矩形(单调栈/暴力)

在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;

int fun(vector<int>& heights){
	stack<int> s;
	int size = heights.size();
	int res = 0;
	for(int i=0;i<size;i++){
		while(!s.empty() && heights[s.top()] > heights[i]){
			int length = heights[s.top()];
			s.pop();
			int weight = i;
			if(!s.empty()){
				weight = i - s.top() - 1;
			}
			res = max(res, length*weight);
		}
		s.push(i);
	}

	while(!s.empty()){
		int length = heights[s.top()];
		s.pop();
		int weight = size;
		if(!s.empty()){
			weight = size - s.top() -1;
		}
		res = max(res, length*weight);
	}
	return res;
}

int main(){
	vector<int> heights={2, 1, 5, 6, 2, 3};
	cout<<fun(heights)<<endl;
}

单调栈

https://leetcode-cn.com/problems/largest-rectangle-in-histogram/solution/bao-li-jie-fa-zhan-by-liweiwei1419/

42.接雨水

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;

int fun(int height[],int len){
	stack<int> s;
	int res = 0;
	//cout<<len<<endl;
	for(int i=0;i<len;i++){
		//cout<<i<<" "<<endl;
		while(!s.empty() && height[i] > height[s.top()]){
			int bottom = height[s.top()];s.pop();
			if(s.empty()){
				break;
			}
			int weight = i - s.top() - 1;
			int top= min(height[i],height[s.top()]);
			res = res + (weight*(top-bottom));
			cout<<"i="<<i<<" res="<<res<<endl;
		}
		s.push(i);
	}
	cout<<res<<" ";
	return res;
}

int main(){
	int a[] = {0,1,0,2,1,0,1,3,2,1,2,1};
	int len = sizeof(a)/sizeof(a[0]);
	fun(a,len);
} 

59-1滑动窗口最大值 – 双向队列deque

46.全排列 – dfs

在这里插入图片描述

class Solution {
public:

    int len = 0;
    void fun(vector<vector<int>> &s,vector<int> &single,int first){
        if(first == len){
            s.emplace_back(single);
            return ;
        }
        for(int i=first;i<len;i++){
            swap(single[first],single[i]);
            fun(s,single,first+1);
            swap(single[first],single[i]);
        }
    }

    vector<vector<int>> permute(vector<int>& nums) {
        len = nums.size();
        vector<vector<int>> s;
        fun(s,nums,0);
        return s;
    }
};

415.大数相加

假装位数一样不够的前面补零

class Solution {
public:
    string addStrings(string num1, string num2) {
        int l1=num1.length()-1,l2=num2.length()-1;
        int carry=0;
        string s="";
        while(carry || l1>=0 || l2>=0){
            int x = l1<0?0:num1[l1--]-'0';
            int y = l2<0?0:num2[l2--]-'0';
            s.insert(0,1,char((x+y+carry)%10)+'0');
            carry=(x+y+carry)/10;
        }
        return s;
    }
};

03.数组中的重复数字

找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1
的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

示例 1:

输入: [2, 3, 1, 0, 2, 5, 3] 输出:2 或 3

  1. 二重循环 失败原因:超时
  2. hashset 失败原因:只有java有hashset,c++没有这个数据结构
class Solution {
    public int findRepeatNumber(int[] nums) {
        Set<Integer> set = new HashSet<Integer>();
        int repeat = -1;
        for (int num : nums) {
            if (!set.add(num)) {
                repeat = num;
                break;
            }
        }
        return repeat;
    }
}
  1. bool数组将原数组作为索引 成功
class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
      bool b[nums.size()];
      memset(b,false,sizeof(b));
      for(int i=0;i<nums.size();i++){
          if(b[nums[i]]==false){
              b[nums[i]] = true;
          }else{
              cout<<nums[i]<<endl;
              return nums[i];
          }
      }
      return -1;
    }
};

21. 调整数组顺序使奇数位于偶数前面

两种很好的方法:首尾双指针和快慢双指针
在这里插入图片描述

在这里插入图片描述

//首尾双指针
vector<int> fun1(vector<int>& nums){
	int l = 0,r = nums.size()-1;
	while(l<r){
		if(nums[l] %2 ==1){
			l++;
			continue;
		}
		if(nums[r] %2 ==0){
			r--;
			continue;
		}
		swap(nums[l],nums[r]);
	}
	for(int i=0;i<nums.size();i++){
		cout<<nums[i]<<" ";
	}
	return nums;
}

在这里插入图片描述

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;

//快慢双指针
vector<int> fun2(vector<int>& nums){
		int low=0,fast=0;
		while(fast < nums.size()){
			if(nums[fast] % 2 == 1){
				swap(nums[low],nums[fast]);
				low++;
			}
			fast++;
		}
	for(int i=0;i<nums.size();i++){
		cout<<nums[i]<<" ";
	}
	return nums;
}

int main(){
	vector<int> nums{1,3,4,5,6,7,2,9,8};
	fun1(nums);
	cout<<endl;
	fun2(nums);
}

30.包含min函数的栈

在这里插入图片描述
在这里插入图片描述

class MinStack {
public:
    /** initialize your data structure here. */
    stack<int> a,b;
    MinStack() {
        
    }
    
    void push(int x) {
        a.push(x);
        if(b.empty() || b.top() <= x) b.push(x);
    }
    
    void pop() {
        int x = a.top();a.pop();
        if(b.top() == x) b.pop();
    }
    
    int top() {
        return a.top();
    }
    
    int min() {
        return b.top();
    }
};

31. 栈的压入弹出序列

在这里插入图片描述

class Solution {
public:
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
        stack<int> s;
        int j=0;
        cout<<pushed.size()<<endl;
        for(int i=0;i<pushed.size();i++) {
            s.push(pushed[i]); //先放进去再说
            while(!s.empty() && s.top() == popped[j]){
                s.pop();
                j++;
            }
        }
        return s.empty();
    }
};

04. 二维数组中的查找

在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

示例:

现有矩阵 matrix 如下:

[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]

给定 target = 5,返回 true。

给定 target = 20,返回 false。
  1. 先查找左边界,再锁定到具体行,行内排查。 失败:并不是下一行开头就比上一行结尾大,题意理解不当。
  2. 找规律:
    在这里插入图片描述note:必须要有判断矩阵是否为空的判断,否则无法执行。
class Solution {
public:
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
         if (matrix.size() == 0){
            return false;
        }
        int n =matrix.size();
        int m = matrix[0].size();
        int i=n-1;
        int j=0;
        while(i>=0 && j <m){
            if(target == matrix[i][j]){
                return true;
            }else if(target < matrix[i][j]){
                i--;
               //cout<<matrix[i][j]<<" i--";
            }else{
                j++;
                //cout<<matrix[i][j]<<" j++";
            }
        }
        return false;
    }
};
  1. 递归
    在 midmidmid 列寻找满足条件
 matrix[row − 1][mid] < target < matrix[row][mid]

的点,比如当 row=3,mid=2时(黄色区域),9<target<14,这时我们可以判断出来 target 一定在左下或者右上区域:

由 target>9,可知 target在 9 的右侧或下侧;
由 target<14,可知 target在 14的上侧或左侧;

因此对左下和右上两个区域进行递归,直到遇到终止条件进行回溯,返回结果。 终止条件为:

区域中没有元素;
target大于深色区域右下角的值(最大值)或小于深色区域左上角的值(最小值)

其中,找到黄色点的方法如下:

列索引 mid采用二分查找;
行索引沿 mid 列从上向下移动,并保持该位置元素小于 target。

在这里插入图片描述### 05. 替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

没有开辟额外空间,先根据空格数量在字符串末尾扩容两个字符的空间(因为一个空格变为%20需要多出两个空间),
然后倒叙遍历将原来位置的字符放到后面, 最后返回s就可以了.

class Solution {
public:
    string replaceSpace(string s) {
       int l1 = s.length()-1;
        for (int i = 0; i <= l1; i++) {
            if (s[i] == ' ') {
                s += "00";
            }
        }
        int l2 = s.length() - 1;
        if (l2 <= l1) {
            return s;
        }
        for (int i = l1; i >= 0; i--) {
            char c = s[i];
            if (c == ' ') {
                s[l2--] = '0';
                s[l2--] = '2';
                s[l2--] = '%';
            } else {
                s[l2--] = c;
            }
        }
        return s;

    }
};

48.旋转图像(矩阵)

在这里插入图片描述

采用分层来进行平移的方式,将矩阵的每一层都分开进行旋转,比如5*5的矩阵可以分为3层
矩阵分层.png
旋转的时候,每四个矩阵块作为一组进行相应的旋转
图片.png
image.png
可以看出,第二次旋转的时候比第一次旋转偏移了一格,这里我们使用add变量来记录矩阵块的偏移量,首先不考虑偏移量的时候写出左上角的坐标为(pos1,pos1),右上角的坐标为(pos1,pos2),左下角的坐标为(pos2,pos1),右下角的坐标为(pos2,pos2),则能够写出偏移之后对应的坐标
坐标变换.png
每次计算完一层之后,矩阵向内收缩一层,
矩阵向内收缩图.png
所以有pos1 = pos1+1,pos2 = pos2-1,终止的条件为pos1 < pos2

class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int p1=0,p2=matrix.size()-1;
        while(p1<p2){ //每一层
            int add=0;
            while(add<p2-p1){ 
                int tmp=matrix[p1+add][p2];
                matrix[p1+add][p2] = matrix[p1][p1+add];
                matrix[p1][p1+add] = matrix[p2-add][p1];
                matrix[p2-add][p1] = matrix[p2][p2-add];
                matrix[p2][p2-add] = tmp;
                add +=1;  //每一层中每个点的迁移
            }
            p1+=1; //收缩层
            p2-=1;
        }
    }
};

12.矩阵中的路径(dfs)

在这里插入图片描述

dfs+剪枝

class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        rows = board.size();
        cols = board[0].size();
        for(int i = 0; i < rows; i++) {
            for(int j = 0; j < cols; j++) {
                if(dfs(board, word, i, j, 0)) return true;
            }
        }
        return false;
    }
private:
    int rows, cols;
    bool dfs(vector<vector<char>>& board, string word, int i, int j, int k) {
        if(i >= rows || i < 0 || j >= cols || j < 0 || board[i][j] != word[k]) return false; //不是起点或者越界
        if(k == word.size() - 1) return true;
        board[i][j] = '\0';
        bool res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) || 
                      dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1);
        board[i][j] = word[k];
        return res;
    }
};

29.顺时针旋转矩阵

在这里插入图片描述

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& m) {
        vector<int> s;
        if(m.size() == 0) return s;
        int l=0,r=m[0].size()-1,t=0,b=m.size()-1,x=0;
        
        while(true){
            for(int i = l; i <= r; i++) s.push_back(m[t][i]);
            if(++t  > b) break;
            for(int i = t; i <= b; i++) s.push_back(m[i][r]);
            if(--r < l) break;
            for(int i = r; i >= l; i--) s.push_back(m[b][i]);
            if(--b < t) break;
            for(int i = b; i >= t; i--) s.push_back(m[i][l]);
            if(++l > r) break;
        }
        for(vector<int>::iterator it = s.begin(); it != s.end(); it++){
            cout<<*it<<" ";
        }
        return s;
    }
};

56-1.数组中数字出现的次数

位异或,不要急拿笔演算一下试一试,但是只适用于双数出现中挑选出单数出现的,局限性比较大
在这里插入图片描述

public int[] singleNumbers(int[] nums) {
        //xor用来计算nums的异或和
        int xor = 0;

        // 计算异或和 并存到xor
        // e.g. [2,4,2,3,3,6] 异或和:(2^2)^(3^3)^(4^6)=2=010
        for(int num : nums) xor ^= num;

        //设置mask为1,则二进制为0001
        // mask是一个二进制数,且其中只有一位是1,其他位全是0,比如000010,
        // 表示我们用倒数第二位作为分组标准,倒数第二位是0的数字分到一组,倒数第二位是1的分到另一组
        int mask = 1;

        // & operator只有1&1时等于1 其余等于0
        // 用上面的e.g. 4和6的二进制是不同的 我们从右到左找到第一个不同的位就可以分组 4=0100 6=0110
        // 根据e.g. 010 & 001 = 000 = 0则 mask=010
        // 010 & 010 != 0 所以mask=010
        // 之后就可以用mask来将数组里的两个数分区分开
        while((xor & mask)==0){
            mask <<= 1;
        }

        //两个只出现一次的数字
        int a=0, b=0;

        for(int num : nums){
            //根据&是否为0区分将两个数字分区,并分别求异或和
            if((num & mask)==0) a ^= num;
            else{
                b ^= num;
            }
        }
        return new int[]{a,b};
    }

56-2

map的写法,比较倾向于是一个通法。
在这里插入图片描述

class Solution {
public:
    int singleNumber(vector<int>& nums) {
    	//哈希表方法
        map<int,int> mp;
	    for(int i=0;i<nums.size();i++){
		    mp[nums[i]]++;
		    cout<<nums[i]<<":"<<mp[nums[i]]<<endl;
        }
        int ans;
        for(map<int,int>::iterator it = mp.begin();it!=mp.end();it++){
            if(it->second == 1){
                ans = it->first;
                cout<<ans<<endl;
                break;
            }
        }
        return ans; 
    }
};

方法二:位运算

  1. 值得注意的是:如果某个数字出现3次,那么这个3个数字的和肯定能被3整除,则其对应二进制位的每一位的和也能被3整除
  2. 统计数组中每个数字的二进制中每一位的和,判断该和是否能被3整除。
  3. 若可以,则只出现一次的数字的二进制数中那一位为0,否则为1
    话不多说看不懂的话没关系,推演一下就明白了,这个方法也可以作为通式通法
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        //位运算方法
        int ans = 0;
        for(int i=0;i<32;i++){
            int cnt = 0;
            for(int j=0;j<nums.size();j++){
                if(nums[j] & (1<<i)) cnt++;
            }
            if(cnt % 3 == 1) ans ^= (1<<i);
        } 
        cout<<ans;
        return ans;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值