Leetcode刷题防忘录(五)

 

目录

 

set-matrix-zeroes(回溯,中等)

combination-sum(回溯,DFS,中等)

valid-sudoku(回溯,中等)

search-insert-position(二分法,简单)

search-for-a-range(遍历,简单)

generate-parentheses(回溯,DFS,中等)


set-matrix-zeroes(回溯,中等)

给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用原地算法。

示例 1:

输入: 
[
  [1,1,1],
  [1,0,1],
  [1,1,1]
]
输出: 
[
  [1,0,1],
  [0,0,0],
  [1,0,1]
]
示例 2:

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

思路

第一个大循环,先去找每个零,找到了把对应的上下左右,不是0的位给置成"-1",然后,再来一个大循环,把上一个循环置成"-1"的位,给全部置成“0”,就完成操作了。这是因为,如果第一次就置成“-1”,后面找“0”的时候,会没办法区分开到底是原先的0啊,还是新置成的0了。

PS:这个思路是典型的回溯思想,面试的时候被问到过,这个思路可以直接在输入矩阵上修改。故可以节省空间。

完整code

class Solution {
public:
	void setZeroes(vector<vector<int> > &matrix) {
		int m = matrix.size();
		int n = matrix[0].size();
		for (int i = 0; i < m; i++)
		{
			for (int j = 0; j < n; j++)
			{
				if (matrix[i][j] == 0)
				{
					for (int x = 0; x < n; x++)
						if(matrix[i][x] != 0)
						   matrix[i][x] = -1;
					for (int y = 0; y < m; y++)
						if (matrix[y][j] != 0)
						   matrix[y][j] = -1;
				}
			}
		}
		for (int i = 0; i < m; i++)
		{
			for (int j = 0; j < n; j++)
			{
				if (matrix[i][j] == -1)
					matrix[i][j] = 0;
			}
		}
	}
};

combination-sum(回溯,DFS,中等)

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

所有数字(包括 target)都是正整数。
解集不能包含重复的组合。 
示例 1:

输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
  [7],
  [2,2,3]
]
示例 2:

输入: candidates = [2,3,5], target = 8,
所求解集为:
[
  [2,2,2,2],
  [2,3,3],
  [3,5]
]

思路

在主函数combinationSum中,首先,将输入进行排序,这个相当于预处理,保证了后面都是按升序来进行。之后,就进入了Sum中,其中,res是存最后的结果数组,temp是存每次进行的处理结果的数组,candidates是输入待处理的数组,后面的第四个参数,是记录每次待处理的元素的下标,target是输入的目标值。

进入Sum函数,先定义出口,和边界条件判断

        if (target == 0)//定义递归出口
        {
            res.push_back(temp);
            return;
        }
        if (target < 0||k >= candidates.size())//边界条件
            return;

接着,将上一次的temp的值存入到t里面,然后将这次待比较的candidates中的元素,存入到temp中来,

        vector<int>t=temp;
        temp.push_back(candidates[k]);

那么精彩之处就来了!把temp给带到递归中去,分两个递归, 如果用当前处理的candidates[k]值,我们可以用来累加得到target,就将这个值压入temp中,并且第四个参数还设置成k,意思是说,我就让candidates[k]一直带入,看看能不能最后得到target(比如,target是6,而candidates里面包含了一个2,那么肯定存在一种可能就是,2+2+2=6,这一步就是为了包含这种情况,有某个元素,在之前累加的基础上,只自己重复累加就可以得到target了。)

      如果不用当前处理的candidates[k]值,那么就把t给带进去,因为t是保存的上一次的temp不包含这次的candidates[k],并且,将k+1,开始检查下一个元素。target不变。

整个code最巧妙的方法就在于这儿,通过模拟用或者不用,将所有可能都囊括了其中,每一步都递归出很多可能,最后,被边界条件限制给抹杀掉不合适的,用递归出口存下来符合要求的,很是巧妙。让它们自己繁殖然后,在杀掉不合适的,把合适的保存下来,感觉像造物主一样伟大!牛逼。

        Sum(res, temp, candidates, k , target- candidates[k]);//用
        Sum(res, t, candidates, k + 1, target);//不用

完整code 

class Solution {
public:
    vector<vector<int> > combinationSum(vector<int> &candidates, int target) {
        vector<vector<int> > res;
        vector<int> temp;
        sort(candidates.begin(), candidates.end());
        Sum(res, temp, candidates, 0, target);
        return res;
    }
    void Sum(vector<vector<int>> &res, vector<int> &temp, vector<int> &candidates, int k, int target) {
        if (target == 0)
        {
            res.push_back(temp);
            return;
        }
        if (target < 0||k >= candidates.size())
            return;
        vector<int>t=temp;
        temp.push_back(candidates[k]);
        Sum(res, temp, candidates, k , target- candidates[k]);//用
        Sum(res, t, candidates, k + 1, target);//不用
    }
};

valid-sudoku(回溯,中等)

判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。

1.数字 1-9 在每一行只能出现一次。 2.数字 1-9 在每一列只能出现一次。 3.数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

上图是一个部分填充的有效的数独。数独部分空格内已填入了数字,空白格用 '.' 表示。

示例 1:

输入:
[
  ["5","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
输出: true
示例 2:

输入:
[
  ["8","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
输出: false
解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。
     但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。

思路

本来这个思路用字典来做,特别适合,将每个位置的值记为字典的键,然后出现的次数记为值,如果值大于1就是无效的了。

但下面这个思路也非常巧妙。

首先,设置三个数组,分布代表记录横着的,数着的和3*3小格子的统计情况。将每个格子都填成了0,这就是整个Code最巧妙的地方了!

        int row[9][9] = {0};
        int col[9][9] = {0};
        int grids[3][3][9] = {0};

接着往下看,是一个大循环,一个循环就把结果给算出来了,也是很牛逼啊!

        for(int i = 0; i < 9; i++) {
            for(int j = 0; j < 9; j++) {
            }
        }

 循环遍历每个数组值,如果当前数组值不为空,就进行判断,真正精彩的地方来了,先是定义了index,为什么要减1呢?因为索引都是0开始的,而数组里的数字是1~9,减一是为了让范围变成0~8,对应上下标。

然后,在if语句里面一个++语句,这个表达的就非常抽象了,经过苦思冥想,终于明白了作者的意思。刚开始创建数组的时候,每个位置都是0,而0对应的bool值是false,其他数字对应的bool都是true,那到这里,开始遍历,还要明白一点,如果再某一行有重复的字符出现,那么这两个对应的index也肯定是一样的,以row为例:

首先if判断row[i][index]是ture还是false,如果是0,就不进入循环了,而且,每一步都会执行row[i][index]++,即,row[i][index] = row[i][index] + ‘1’,这样,就把原本是0的值,给加进去一个值了,变成1了,那么这个bool类型也变成true了,那么下一次如果有相同的index出现,再次跳到这个row[i][index]来判断的时候,if是肯定会发现这个地方是已经有值存在了,就会进入if里面,执行returne false了。

                    int index = board[i][j] - '1';
                    if(row[i][index]++)
                        return false;
                    if(col[j][index]++)
                        return false;
                    if(grids[i/3][j/3][index]++)
                        return false;

完整code

class Solution {
public:
    bool isValidSudoku(vector<vector<char> > &board) {
        int row[9][9] = {0};
        int col[9][9] = {0};
        int grids[3][3][9] = {0};
        for(int i = 0; i < 9; i++) {
            for(int j = 0; j < 9; j++) {
                if(board[i][j] != '.') {
                    int index = board[i][j] - '1';
                    if(row[i][index]++)
                        return false;
                    if(col[j][index]++)
                        return false;
                    if(grids[i/3][j/3][index]++)
                        return false;
                }
            }
        }
        return true;
    }
};

search-insert-position(二分法,简单)

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

你可以假设数组中无重复元素。

示例 1:

输入: [1,3,5,6], 5  输出: 2
示例 2: 

输入: [1,3,5,6], 2  输出: 1
示例 3:

输入: [1,3,5,6], 7  输出: 4
示例 4:

输入: [1,3,5,6], 0  输出: 0

思路

二分法是拿mid去更新起末位置,而不是反过来。

如果要找插入的位置是,从前往后数索引,找到待插位置,不是从后往前。

完整code

class Solution {
public:
	int searchInsert(int A[], int n, int target) {
		int l = 0;
		int r = n - 1;
		
		while (l <= r)
		{
			int mid = (r + l) / 2;
			if (A[mid] == target) return mid;
			if (A[mid] > target) r = mid - 1;
			if (A[mid] < target) l = mid + 1;
		}
		return l;
	}
};

search-for-a-range(遍历,简单)

给出一个有序数组,请在数组中找出目标值的起始位置和结束位置

你的算法的时间复杂度应该在O(log n)之内

如果数组中不存在目标,返回[-1, -1].

例如:

给出的数组是[5, 7, 7, 8, 8, 10],目标值是8, 返回[3, 4].

思路

分两个阶段,第一阶段,先在数组中遍历一遍,如果有数组中与目标值相等的元素,那就把位置索引给保存到index里面。

第二个阶段,如果index为空,说明数组中没有找到与目标值相等的元素,那么,就返回[-1,-1];

如果不为空,那么就把索引的最小的和最大的,作为值,返回出去。

此题,默认输入数组已经是升序排列好了,所以,如此简单。

完整code

class Solution {
public:
	vector<int> searchRange(int A[], int n, int target) {
		vector<int> res;
		vector<int> index;
		for (int i = 0; i < n; i++)
		{
			if (A[i] == target)
			    index.push_back(i);
		}
		if (index.size() != 0)
		{
			res.push_back(index[0]);
			int ns = (int)index.size();
			res.push_back(index[ns-1]);
			return res;
		}
		else
		{
			res.push_back(-1);
			res.push_back(-1);
			return res;
		}
	}
};

generate-parentheses(回溯,DFS,中等)

给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。

例如,给出 n = 3,生成结果为:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]

思路

主要是辅助函数func,输入第一个参数tmp,用来存放每次生成的新括号序列,后面两个参数,记录左、右两半边括号的个数。

设置递归出口,如果左右括号数同时全部归零,说明都用完了,可以返回,因为返回类型是void,所以,,没有return也是可以的,直接将生成的tmp,存到了结果res里面。并且清空tmp,便于下一次存放新的括号序列。

		if (l == 0 && r == 0) {
			res.push_back(tmp);
			tmp.clear();
		}

接着,如果左括号数没用完,或者说,右边没用完的括号数量比左边的大,都需要继续进行递归。

如果左边数没用完,下一次递归的时候,就直接在tmp里面添一个“(”,同时l-1。当(r>l)的时候,也是如此。

这个地方需要注意的是,会不会出现:“))((”的情况出现?不会的,这也是这个Code的精彩的地方,整个思路其实是利用了递归回调的时候,来进行的。因为判断(l>0)这个条件在前,所以,一开始,肯定有落单的左括号,然后,只要有左括号落单,就会有右括号去填补(r>l)而且,也保证了右括号至多和左括号相等不会超过左括号的数量,所以,无形中,其实是按照n个连续左括号,n-1个连续左括号,n-2个连续左括号。。。。的思路,来进行的。

 

			if (l > 0) {
				func(tmp + '(', l - 1, r);
			}
			if (r > l) {
				func(tmp + ')', l, r - 1);
			}

完整code

class Solution {
public:
	vector<string> res;
	vector<string> generateParenthesis(int n) {
		if (n <= 0)
			return res;
		string tmp;
		func(tmp, n, n);
		return res;
	}
	void func(string tmp, int l, int r) {
		if (l == 0 && r == 0) {
			res.push_back(tmp);
			tmp.clear();
		}
		else {
			if (l > 0) {
				func(tmp + '(', l - 1, r);
			}
			if (r > l) {
				func(tmp + ')', l, r - 1);
			}
		}
	}
};

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值