class Solution {
public:
int mySqrt(int x) {
int l = 0, r = x, ans = -1;
while (l <= r) {
int mid = l + (r - l) / 2;
if ((long long)mid * mid <= x) {
ans = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
return ans;
}
};
class Solution {
public:
int mySqrt(int x) {
int l = 0, r = x;
while (l <= r)
{
int mid = l + (r - l) / 2;
if ((long long)mid * mid <= x)
{
// ans = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
return r;
//return l-1;
}
};
class Solution {
public:
bool isPerfectSquare(int num) {
int l = 0;
int r = num;
while( l <= r ){
long m = l + (r-l)/2;
long sqrt = m*m;
if( sqrt == num ) { return true; }
else if(sqrt > num ) { r = m-1; }
else { l = m+1;}
}
return false;
}
};
其实她利用了一个技巧,当我们一分为二的时候,只要子序列的尾端点大于头端点,那么他就是有序的。而且毕竟一个有序一个没序
在判断中分四种情况如下,也通过这也来写的代码
左边有序
右边有序
需要注意进行的两个特判
class Solution {
public:
int search(vector<int>& nums, int target) {
int n = (int)nums.size();
if (!n)//长度为0
{
return -1;
}
if (n == 1) //长度为1
{
if (nums[0]==target) return 0;
else -1;
}
int l = 0, r = n - 1;//确定左边界 和右边界
while (l <= r)
{
int mid = l+(r-l) / 2;
if (nums[mid] == target) return mid;//符合条件
if (nums[0] <= nums[mid])//前半段有序
{
if (nums[0] <= target && target < nums[mid])//并且目标在这个区间
{
r = mid - 1;//在左半部分
} else {
l = mid + 1;//在右半部分
}
} else //后半段有序
{
if (nums[mid] < target && target <= nums[n - 1])//并且在这个区间
{
l = mid + 1;
} else
{
r = mid - 1;//不在这个区间
}
}
}
return -1;
}
};
方法一
:由于每行的第一个元素大于前一行的最后一个元素,且每行元素是升序的,所以每行的第一个元素大于前一行的第一个元素,因此矩阵第一列的元素是升序的。我们可以对矩阵的第一列的元素二分查找,找到最后一个不大于目标值的元素,然后在该元素所在行中二分查找目标值是否存在。
方法二
:若将矩阵每一行拼接在上一行的末尾,则会得到一个升序数组,我们可以在该数组上二分找到目标元素。代码实现时,可以二分升序数组的下标,将其映射到原矩阵的行和列上。
第一个方法代码没怎么看懂
还是第二个方法好,巧妙运用 / % 来得到所在行和所在列 而不是真正地拼接了它们
区别在于
int m = matrix.size(), n = matrix[0].size();
// 得到矩阵行和列int left = 0, right = m * n - 1;
// 左边位置和右边位置int x = matrix[mid / n][mid % n];
// 善于利用矩阵位置和除法运算与取模运算的关系当然还有一种方法就是利用右上角的那个值,如果比它小去掉所在列,如果比它大去掉所在行。重新判断对角的那个点,最后我们要的值一定能找到
class Solution {
public:
bool searchMatrix(vector<vector<int>> matrix, int target) {
auto row = upper_bound(matrix.begin(), matrix.end(), target, [](const int b, const vector<int> &a) {
return b < a[0];
});
if (row == matrix.begin()) {
return false;
}
--row;
return binary_search(row->begin(), row->end(), target);
}
};
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) { // 二分法,核心思想是把矩阵拉成一条数组。
// 可行的原因是每一行开头的数值比上一行末尾的大。可以把下一行拼接到上一行,最后将矩阵变成一行。
int m = matrix.size(), n = matrix[0].size(); // 得到矩阵行和列
int left = 0, right = m * n - 1; // 左边位置和右边位置
while (left <= right) { // 二分法套路
int mid = (right - left) / 2 + left; // 这样设置防止溢出的风险
int x = matrix[mid / n][mid % n]; // 善于利用矩阵位置和除法运算与取模运算的关系
if (x < target) { // 目标数值太大
left = mid + 1;
} else if (x > target) { // 目标数值小
right = mid - 1;
} else {
return true; // 找到了
}
}
return false; // 没找到
}
};
官方就是 这个最小值一定是在 一段序列中 最左大于最右的那个序列
第二个理解就是 翻转一定是两个序列,而且最小值一定在第二个序列,如果有序则说明不在那一边
看代码需要理解的一种就是 当mid 小于 r的时候r = mid
没有-1 你想想 是不是有最小值在中间点的特殊情况 不要漏掉了,特比的地方
还有个需要注意的是返回值 ,我常用的二分查找,就是 <-= 就是 -1 你也可以换成< 那就直接返回 l 因为当退出 说明 r=l 也就是区间只有一个数 那必然这个数就是我们要的
注意返回值 要么 是 r 要么是 l-1
class Solution {
public:
int findMin(vector<int>& nums) {
int l = 0;
int r = nums.size() - 1;
while (l <= r)
{
int mid = l + (r - l) / 2;
if (nums[mid] < nums[r])//如果中间小于右边 说明在前半部分
{
r = mid;
}
else //反之在后半部分
{
l = mid + 1;
}
}
return nums[r];
}
};
小小复习
递归三步走
分治
回朔(常见类型题 自己看 选啊 还是b啊这种)
贪心算法
局部最优推导全局最优
动态规划关键点
比较经典的一个题目,知道就简单了,难点在于想到状态转移的方程,这个题的时间复杂度和空间复杂度都是o(mn)
好好看看vector如何初始化大小和初始值 一维和二维
还有一个就是这个坐标系的建议不要死脑经,他都是以目标店作为原点,出发点作为终止点。
class Solution {
public:
int uniquePaths(int m, int n) {
//m行 n列
vector<vector<int>> f(m,vector<int>(n));
for(int i=0;i<m;i++)
{
f[i][0]=1;//操作的是行
}
for (int j = 0; j < n; ++j)
{
f[0][j] = 1;
}
for (int i=1;i<m;i++)//需要注意这边是从1开始
{
for(int j=1;j<n;j++)
{
f[i][j]=f[i - 1][j] + f[i][j - 1];
}
}
return f[m - 1][n - 1];
}
};
非常重要补充
所以在遇到求方案数的问题时,我们可以往动态规划的方向考虑。
多了和判断 就是状态转移方程变成 两个 一个是0 一个是正常的
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int m = obstacleGrid.size();
int n = obstacleGrid[0].size();
vector<vector<int>> dp(m, vector<int>(n, 0));
for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) dp[i][0] = 1;
for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) dp[0][j] = 1;
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (obstacleGrid[i][j] == 1) continue;
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
};
简单的说 状态把两个字符串放在二维的空间上。
状态转移方程有两种情况
有一个问题就是不是很理解我自己写的第二种为什么不行,这个相比机器人就是i=0可以,但是i=-1不行啊 所以全部+1了。反正这种问题,你需要多考虑边界问题
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int m = text1.length(), n = text2.length();
vector<vector<int>> dp(m + 1, vector<int>(n + 1));//构建二维
for (int i = 1; i <= m; i++)
{
char c1 = text1.at(i - 1);//取出字母
for (int j = 1; j <= n; j++)
{
char c2 = text2.at(j - 1);//取出字母
if (c1 == c2) //如果两个字母
{
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
};
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int m = text1.length(), n = text2.length();
vector<vector<int>> dp(m , vector<int>(n));//构建二维
for (int i = 0; i < m; i++)
{
char c1 = text1.at(i);//取出字母
for (int j = 0; j < n; j++)
{
char c2 = text2.at(j);//取出字母
if (c1 == c2) //如果两个字母
{
if(i!=0&&j!=0) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j]=1;
} else
{
if(i!=0&&j!=0) dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
else dp[i][j]=0;
}
}
}
return dp[m-1][n-1];
}
};
。需要特别判定就是靠在墙边的那些,以及最右边的那一排 自己画一个三角形在二维的看看就知道了
public:
int minimumTotal(vector<vector<int>>& triangle) {
int n = triangle.size();
vector<vector<int>> f(n, vector<int>(n));
f[0][0] = triangle[0][0];
for (int i = 1; i < n; ++i) {
f[i][0] = f[i - 1][0] + triangle[i][0];
for (int j = 1; j < i; ++j) {
f[i][j] = min(f[i - 1][j - 1], f[i - 1][j]) + triangle[i][j];
}
f[i][i] = f[i - 1][i - 1] + triangle[i][i];
}
return *min_element(f[n - 1].begin(), f[n - 1].end());
}
};
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
vector<int> dp(triangle.back());
for(int i = triangle.size() - 2; i >= 0; i --)
for(int j = 0; j <= i; j ++)
dp[j] = min(dp[j], dp[j + 1]) + triangle[i][j];
return dp[0];
}
};
注意:两个代码在下面,简洁版本的是因为只保留了两个两个变量pre上一个元素以及maxAns用来保存全部的最大值的,在便利过程中就不断比较
class Solution
{
public:
int maxSubArray(vector<int> &nums)
{
//类似寻找最大最小值的题目,初始值一定要定义成理论上的最小最大值
int result = INT_MIN;
int numsSize = int(nums.size());
//dp[i]表示nums中以nums[i]结尾的最大子序和
vector<int> dp(numsSize);
dp[0] = nums[0];
result = dp[0];
for (int i = 1; i < numsSize; i++)
{
dp[i] = max(dp[i - 1] + nums[i], nums[i]);
result = max(result, dp[i]);
}
return result;
}
};
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int pre=0;//你想想状态转移方程要考虑0前面那个,初始为0比较好
int maxAns=nums[0];//这边初始不能是0 如果一开始是-1 只有这一个就错了
for(int i=0;i<nums.size();i++)
{
pre=max(pre+nums[i],nums[i]);
maxAns=max(maxAns,pre);
}
return maxAns;
}
};
class Solution
{
public:
int maxSubArray(vector<int> &nums)
{
//类似寻找最大最小值的题目,初始值一定要定义成理论上的最小最大值
int result = INT_MIN;
int numsSize = int(nums.size());
int sum = 0;
for (int i = 0; i < numsSize; i++)
{
sum += nums[i];
result = max(result, sum);
//如果sum < 0,重新开始找子序串
if (sum < 0)
{
sum = 0;
}
}
return result;
}
};
作者:pinku-2
链接:https://leetcode-cn.com/problems/maximum-subarray/solution/zui-da-zi-xu-he-cshi-xian-si-chong-jie-fa-bao-li-f/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这边可以看看代码随想录的代码
动态规划5步骤
1 确定dp数组以及下标的含义
2 确定递推公式
3 dp数组如何初始化,根据递推公式
4 确定遍历顺序
以这个题分析
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.size() == 0) return 0;
if (nums.size() == 1) return nums[0];
vector<int> dp(nums.size());
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
for (int i = 2; i < nums.size(); i++) {
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[nums.size() - 1];
}
};
添加链接描述
// 版本一
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len =prices.size();
vector<vector<int>> dp(len,vector<int>(2));
dp[0][0]=-prices[0];//第一天持有股票
dp[0][1]=0;//第一天不持有股票
for(int i=1;i<len;i++)//从第二天开始
{
dp[i][0]=max(dp[i-1][0],-prices[i]);//今天持有股票 昨天就有了 今天才买
dp[i][1]=max(dp[i-1][1],prices[i]+dp[i-1][0]);//今天不持有股票 昨天不持有 今天才卖
}
return dp[len - 1][1];//注意看这边是返回最后一天不持有股票的利润
}
};
// 版本二
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
vector<vector<int>> dp(2, vector<int>(2)); // 注意这里只开辟了一个2 * 2大小的二维数组
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < len; i++) {
dp[i % 2][0] = max(dp[(i - 1) % 2][0], -prices[i]);
dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]);
}
return dp[(len - 1) % 2][1];
}
};
和之前不同的在于可以多次交易
重复一下,dp表示当前剩下的钱。
唯一不同的在于,允许多次交易,那么今天持有股票(可能一昨天就持有,昨天不持有(但是可能是多次买卖了)今天买入 dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
)
对比一下之前的dp[i][0] = max(dp[i - 1][0], - prices[i]);
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
vector<vector<int>> dp(len, vector<int>(2, 0));
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < len; i++) {
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); // 注意这里是和121. 买卖股票的最佳时机唯一不同的地方。
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
}
return dp[len - 1][1];
}
};
// 版本二
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
vector<vector<int>> dp(2, vector<int>(2)); // 注意这里只开辟了一个2 * 2大小的二维数组
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < len; i++) {
dp[i % 2][0] = max(dp[(i - 1) % 2][0], dp[(i - 1) % 2][1] - prices[i]);
dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]);
}
return dp[(len - 1) % 2][1];
}
};
要求最多只能进行两次交易
1 确定dp数组以及下标的含义
2确定递推公式
同理dp[i][2]也有两个操作:
dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]); dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
// 版本一
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() == 0) return 0;
vector<vector<int>> dp(prices.size(), vector<int>(5, 0));
dp[0][1] = -prices[0];
dp[0][3] = -prices[0];
for (int i = 1; i < prices.size(); i++) {
dp[i][0] = dp[i - 1][0];
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] + prices[i]);
dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
}
return dp[prices.size() - 1][4];
}
};
// 版本二
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() == 0) return 0;
vector<int> dp(5, 0);
dp[1] = -prices[0];
dp[3] = -prices[0];
for (int i = 1; i < prices.size(); i++) {
dp[1] = max(dp[1], dp[0] - prices[i]);
dp[2] = max(dp[2], dp[1] + prices[i]);
dp[3] = max(dp[3], dp[2] - prices[i]);
dp[4] = max(dp[4], dp[3] + prices[i]);
}
return dp[4];
}
};
做到这边我想到一个问题,就是你认真看看股票的问题 他返回的结果都是最后面的,dp总是表示到今天为止的最好的结果,他也是用这个来搞递推了 和其他的有点区别
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
if (prices.size() == 0) return 0;
vector<vector<int>> dp(prices.size(), vector<int>(2 * k + 1, 0));
for (int j = 1; j < 2 * k; j += 2) {
dp[0][j] = -prices[0];
}
for (int i = 1;i < prices.size(); i++) {
for (int j = 0; j < 2 * k - 1; j += 2) {
dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
}
}
return dp[prices.size() - 1][2 * k];
}
};
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(2, 0));
dp[0][0] -= prices[0]; // 持股票
for (int i = 1; i < n; i++) {
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
}
return max(dp[n - 1][0], dp[n - 1][1]);
}
};
查看细节描述
class Trie {
private:
bool isEnd;//用来判断是不是结尾
Trie* next[26];//存放的都是节点
public:
Trie()
{
isEnd = false;//初始化成员
memset(next, 0, sizeof(next));//清空数组
}
void insert(string word) {
Trie* node = this;//当前不就是根节点
for (int i=0;i<word.length();i++) {
if (node->next[word[i]-'a'] == NULL)
{
node->next[word[i]-'a'] = new Trie();
}
node = node->next[word[i]-'a'];//这边要记得不断进入下一层
}
node->isEnd = true;//这个也要记得,for循环结束记得标记结尾
}
bool search(string word) {
Trie* node = this;
for (int i=0;i<word.length();i++) {
node = node->next[word[i] - 'a'];
if (node == NULL) {
return false;
}
}
return node->isEnd;//并不是遍历结束就好了 你要看看最后的元素是不是结尾
}
bool startsWith(string prefix)
{
//和搜索基本完全一样,就是不需要判断是不是结尾 直接返回true
Trie* node = this;
for (int i=0;i<prefix.length();i++) {
node = node->next[prefix[i]-'a'];
if (node == NULL) {
return false;
}
}
return true;
}
};
class Solution {
public:
bool check(vector<vector<char>>& board, vector<vector<int>>& visited, int i, int j, string& s, int k) {
if (board[i][j] != s[k]) {
return false;
} else if (k == s.length() - 1) {
return true;
}
visited[i][j] = true;
vector<pair<int, int>> directions{{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
bool result = false;
for (const auto& dir: directions) {
int newi = i + dir.first, newj = j + dir.second;
if (newi >= 0 && newi < board.size() && newj >= 0 && newj < board[0].size()) {
if (!visited[newi][newj]) {
bool flag = check(board, visited, newi, newj, s, k + 1);
if (flag) {
result = true;
break;
}
}
}
}
visited[i][j] = false;
return result;
}
bool exist(vector<vector<char>>& board, string word) {
int h = board.size(), w = board[0].size();
vector<vector<int>> visited(h, vector<int>(w));
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
bool flag = check(board, visited, i, j, word, 0);
if (flag) {
return true;
}
}
}
return false;
}
};
class Solution {
public:
bool DFS(vector<vector<char>>& board, string& word, int tmpi, int tmpj, int k)
{
if (k == word.size()) return true;
bool flag = false;
if (tmpi > 0 && board[tmpi - 1][tmpj] == word[k])
if (!flag)
{
board[tmpi - 1][tmpj] = 0;
flag = DFS(board, word, tmpi - 1, tmpj, k + 1);
board[tmpi - 1][tmpj] = word[k];
}
if (tmpj > 0 && board[tmpi][tmpj - 1] == word[k])
if (!flag)
{
board[tmpi][tmpj - 1] = 0;
flag = DFS(board, word, tmpi, tmpj - 1, k + 1);
board[tmpi][tmpj - 1] = word[k];
}
if (tmpi < board.size() - 1 && board[tmpi + 1][tmpj] == word[k])
if (!flag)
{
board[tmpi + 1][tmpj] = 0;
flag = DFS(board, word, tmpi + 1, tmpj, k + 1);
board[tmpi + 1][tmpj] = word[k];
}
if (tmpj < board[0].size() - 1 && board[tmpi][tmpj + 1] == word[k])
if (!flag)
{
board[tmpi][tmpj + 1] = 0;
flag = DFS(board, word, tmpi, tmpj + 1, k + 1);
board[tmpi][tmpj + 1] = word[k];
}
return flag;
}
bool exist(vector<vector<char>>& board, string word) {
int index = 0;
for (int i = 0; i < board.size(); ++i)
{
for (int j = 0; j < board[0].size(); ++j)
{
if (board[i][j] == word[0])
{
board[i][j] = 0;
if (DFS(board, word, i, j, 1))
return true;
board[i][j] = word[0];
}
}
}
return false;
}
};
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
int nr = grid.size();
if (!nr) return 0;
int nc = grid[0].size();
int num_islands = 0;
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
++num_islands;
dfs(grid, r, c);
}
}
}
return num_islands;
}
bool inArea(vector<vector<char>>& grid,int r,int c)
{
return 0 <= r && r < grid.size() && 0 <= c && c < grid[0].size();
}
void dfs(vector<vector<char>>& grid, int r, int c)
{
if (!inArea(grid,r,c))//如果到达边界直接返回
{
return;
}
if(grid[r][c] != '1')//表示是海或者标记过了 注意这边 设置为2 就是为了防止一直转圈圈
{
return;
}
grid[r][c]=2;//把格子标记位已遍历过的 当前层的逻辑 就是
// 访问上、下、左、右四个相邻结点
dfs(grid, r - 1, c);
dfs(grid, r + 1, c);
dfs(grid, r, c - 1);
dfs(grid, r, c + 1);
}
};
class UnionFind {
public:
UnionFind(vector<vector<char>>& grid) {
count = 0;
int m = grid.size();
int n = grid[0].size();
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] == '1') {
parent.push_back(i * n + j);
++count;
}
else {
parent.push_back(-1);
}
rank.push_back(0);
}
}
}
int find(int i) {
if (parent[i] != i) {
parent[i] = find(parent[i]);
}
return parent[i];
}
void unite(int x, int y) {
int rootx = find(x);
int rooty = find(y);
if (rootx != rooty) {
if (rank[rootx] < rank[rooty]) {
swap(rootx, rooty);
}
parent[rooty] = rootx;
if (rank[rootx] == rank[rooty]) rank[rootx] += 1;
--count;
}
}
int getCount() const {
return count;
}
private:
vector<int> parent;
vector<int> rank;
int count;
};
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
int nr = grid.size();
if (!nr) return 0;
int nc = grid[0].size();
UnionFind uf(grid);
int num_islands = 0;
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
grid[r][c] = '0';
if (r - 1 >= 0 && grid[r-1][c] == '1') uf.unite(r * nc + c, (r-1) * nc + c);
if (r + 1 < nr && grid[r+1][c] == '1') uf.unite(r * nc + c, (r+1) * nc + c);
if (c - 1 >= 0 && grid[r][c-1] == '1') uf.unite(r * nc + c, r * nc + c - 1);
if (c + 1 < nc && grid[r][c+1] == '1') uf.unite(r * nc + c, r * nc + c + 1);
}
}
}
return uf.getCount();
}
};
class Solution {
public:
bool isValidSudoku(vector<vector<char>>& board) {
int row[9][10] = {0};// 哈希表存储每一行的每个数是否出现过,默认初始情况下,每一行每一个数都没有出现过
// 整个board有9行,第二维的维数10是为了让下标有9,和数独中的数字9对应。
int col[9][10] = {0};// 存储每一列的每个数是否出现过,默认初始情况下,每一列的每一个数都没有出现过
int box[9][10] = {0};// 存储每一个box的每个数是否出现过,默认初始情况下,在每个box中,每个数都没有出现过。整个board有9个box。
for(int i=0; i<9; i++){
for(int j = 0; j<9; j++){
// 遍历到第i行第j列的那个数,我们要判断这个数在其所在的行有没有出现过,
// 同时判断这个数在其所在的列有没有出现过
// 同时判断这个数在其所在的box中有没有出现过
if(board[i][j] == '.') continue;
int curNumber = board[i][j]-'0';
if(row[i][curNumber]) return false;
if(col[j][curNumber]) return false;
if(box[j/3 + (i/3)*3][curNumber]) return false;
row[i][curNumber] = 1;// 之前都没出现过,现在出现了,就给它置为1,下次再遇见就能够直接返回false了。
col[j][curNumber] = 1;
box[j/3 + (i/3)*3][curNumber] = 1;
}
}
return true;
}
};
代码随想录的思路
我思考返回值类型是什么,你看看他这个把函数返回值作为判断条件,只要满足了一个就退出所有层函数的写法。
和红黑树相比,AVL树是严格的平衡二叉树,平衡条件必须满足(所有节点的左右子树高度差不超过1)。通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍,因此,红黑树是一种弱平衡二叉树(由于是弱平衡,可以看到,在相同的节点情况下,AVL树的高度低于红黑树)。
红黑树在查找方面和AVL树操作几乎相同。但是在插入和删除操作上,AVL树每次插入删除会进行大量的平衡度计算,红黑树是牺牲了严格的高度平衡的优越条件为代价,它只要求部分地达到平衡要求,结合变色,降低了对旋转的要求,从而提高了性能。红黑树能够以O(log2 n)的时间复杂度进行搜索、插入、删除操作。此外,由于它的设计,任何不平衡都会在三次旋转之内解决。
class Solution {
public:
int hammingWeight(uint32_t n) {
int ret=0;
while(n!=0)
{
n=n&(n-1);
ret++;//看看进入几次循环 就有几个1
}
return ret;
}
};
class Solution {
public:
bool isPowerOfTwo(int n)
{
if(n>0)
{
n=n & (n - 1);
if (n==0) return true;
else return false;
}
return false;
}
};
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
uint32_t ans=0; //初始化
for(int i=0; i<32; ++i){
ans = (ans<<1) | (n&1); //逐位计算,每次将n的末位放入ans中
n>>=1; //右移
}
return ans;
}
};
class Solution {
public:
int totalNQueens(int n) {
return solve(n, 0, 0, 0, 0);
}
int solve(int n, int row, int columns, int diagonals1, int diagonals2) {
if (row == n) {
return 1;
} else {
int count = 0;
int availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2));
while (availablePositions != 0) {
int position = availablePositions & (-availablePositions);
availablePositions = availablePositions & (availablePositions - 1);
count += solve(n, row + 1, columns | position, (diagonals1 | position) << 1, (diagonals2 | position) >> 1);
}
return count;
}
}
};
class Solution {
public:
int countOnes(int x) {
int ones = 0;
while (x > 0) {
x &= (x - 1);
ones++;
}
return ones;
}
vector<int> countBits(int n) {
vector<int> bits(n + 1);
for (int i = 0; i <= n; i++) {
bits[i] = countOnes(i);
}
return bits;
}
};
class LRUCache {
public:
//定义双链表
struct Node{
int key,value;//一个放时间戳 一个放具体的值
Node* left ,*right;//左指针 右指针
Node(int _key,int _value): key(_key),value(_value),left(NULL),right(NULL){} //初始化
}*L,*R;//双链表的最左和最右节点,不存贮值。
int n;
unordered_map<int,Node*>hash;//初始化一个哈希表
void remove(Node* p)//删除操作
{
p->right->left = p->left;//这个都简单 就是简单就该指向
p->left->right = p->right;
}
void insert(Node *p)//插入操作 插入到首个
{
//先安置好l右边原来的指向 再来处理l和p之间的关系
p->right = L->right;
p->left = L;
L->right->left = p;//主要是注意这边是下面的语句的前面
L->right = p;
}
LRUCache(int capacity) {
n = capacity;//这个是容量大小
L = new Node(-1,-1),R = new Node(-1,-1);
L->right = R;
R->left = L;
}
//或许某个元素的值
int get(int key)
{
if(hash.count(key) == 0) return -1; //不存在关键字 key
auto p = hash[key];//有点意思 不知道返回值类型直接用auto
remove(p);//移除
insert(p);//将当前节点放在双链表的第一位
return p->value;
}
//放入
void put(int key, int value) {
if(hash.count(key)) //如果key存在,则修改对应的value
{
auto p = hash[key];
p->value = value;
remove(p);
insert(p);
}
else
{
if(hash.size() == n) //如果缓存已满,则删除双链表最右侧的节点
{
auto p = R->left;
remove(p);
hash.erase(p->key); //更新哈希表
delete p; //释放内存
}
//否则,插入(key, value)
auto p = new Node(key,value);
hash[key] = p;
insert(p);
}
}
};
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache* obj = new LRUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache* obj = new LRUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/
class Node{
public:
int key,value;
Node* right;
Node* left;
Node(int _key,int _value)
{
key=_key;
value=_value;
}
};
class LRUCache {
public:
//四个成员
Node* L;//尾节点
Node* R;//头结点
int n;//容量
unordered_map<int,Node*>hash;//初始化一个哈希表
//构造函数 初始化
LRUCache(int capacity)
{
n = capacity;//这个是容量大小
L = new Node(-1,-1),R = new Node(-1,-1); //这两个节点 没有实际的值并且不插入哈希表
//下面这个好容易忘记 头尾串起来 一开始
L->right = R;
R->left = L;
}
void remove(Node* p)//链表节点的删除操作,并不是真的释放空间 特别注意
{
p->right->left = p->left;//这个都简单 就是简单就该指向
p->left->right = p->right;
}
void insert(Node *p)//链表的插入操作 插入到首个
{
//先安置好l右边原来的指向 再来处理l和p之间的关系
p->right = L->right;
p->left = L;
L->right->left = p;//主要是注意这边是下面的语句的前面
L->right = p;
}
//得到某个元素的值
int get(int key)
{
if(hash.count(key) == 0) return -1; //不存在关键字 key
auto p = hash[key];//有点意思 不知道返回值类型直接用auto
remove(p);//移除,没有释放空间
insert(p);//将当前节点放在双链表的第一位
return p->value;//注意这边 key是int value是地址 我们要返回地址的值
}
//放入
void put(int key, int value)
{
if(hash.count(key)) //如果key存在,则修改对应的value
{
auto p = hash[key];
p->value = value;//直接修改 内部的值
remove(p);//移除 p
insert(p);//插入到开头
}
else
{
if(hash.size() == n) //如果缓存已满,则删除双链表最右侧的节点
{
auto p = R->left;//这个表示实际数据的最后一个
remove(p);
hash.erase(p->key); //更新哈希表,这个很容易忘记
delete p; //释放内存
}
//否则,插入(key, value)
auto p = new Node(key,value);
hash[key] = p;
insert(p);
}
}
};
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache* obj = new LRUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/
class Solution {
public:
bool isAnagram(string s, string t) {
if (s.length()!=t.length())
{
return false;
}
vector<int> table(26,0);
for (int i=0;i<s.length();i++)
{
table[s[i]-'a']++;
}
for (int i=0;i<t.length();i++)
{
table[t[i]-'a']--;
if(table[t[i]-'a']<0)
{
return false;
}
}
return true;
}
};
class Solution {
public:
vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
vector<int> result;//存放结果的
vector<int> freq(1001,0);//辅助的 计数用的
for (int i=0;i<arr1.size();i++)//对arr1进行计数
{
freq[arr1[i]]++;
}
for(int i=0;i<arr2.size();i++)
{
while(freq[arr2[i]]!=0)//数字作为下标 或许数量
{
result.push_back(arr2[i]);//把数字放入到结果
freq[arr2[i]]--;//计数统计少一
}
}
for(int i=0;i<1001;i++)//这个就是把没取完的arr1取出来放入结果 按照递增的
{
while(freq[i]!=0)
{
result.push_back(i);//下标就是数值
freq[i]--;
}
}
return result;
}
};
class Solution {
public:
// 按照区间左边界从小到大排序
static bool cmp (const vector<int>& a, const vector<int>& b) {
return a[0] < b[0];
}
vector<vector<int>> merge(vector<vector<int>>& intervals) {
vector<vector<int>> result;
if (intervals.size() == 0) return result;
sort(intervals.begin(), intervals.end(), cmp);
bool flag = false; // 标记最后一个区间有没有合并
int length = intervals.size();
for (int i = 1; i < length; i++) {
int start = intervals[i - 1][0]; // 初始为i-1区间的左边界
int end = intervals[i - 1][1]; // 初始i-1区间的右边界
while (i < length && intervals[i][0] <= end) { // 合并区间
end = max(end, intervals[i][1]); // 不断更新右区间
if (i == length - 1) flag = true; // 最后一个区间也合并了
i++; // 继续合并下一个区间
}
// start和end是表示intervals[i - 1]的左边界右边界,所以最优intervals[i]区间是否合并了要标记一下
result.push_back({start, end});
}
// 如果最后一个区间没有合并,将其加入result
if (flag == false) {
result.push_back({intervals[length - 1][0], intervals[length - 1][1]});
}
return result;
}
};
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
vector<vector<int>> result;
if (intervals.size() == 0) return result;
// 排序的参数使用了lamda表达式
sort(intervals.begin(), intervals.end(), [](const vector<int>& a, const vector<int>& b){return a[0] < b[0];});
result.push_back(intervals[0]);
for (int i = 1; i < intervals.size(); i++) {
if (result.back()[1] >= intervals[i][0]) { // 合并区间
result.back()[1] = max(result.back()[1], intervals[i][1]);
} else {
result.push_back(intervals[i]);
}
}
return result;
}
};
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end());
vector<vector<int>> ans;
for (int i = 0; i < intervals.size();) {
int t = intervals[i][1];
int j = i + 1;
while (j < intervals.size() && intervals[j][0] <= t) {
t = max(t, intervals[j][1]);
j++;
}
ans.push_back({ intervals[i][0], t });
i = j;
}
return ans;
}
};
class Solution {
public:
string toLowerCase(string s) {
for (int i=0; i<s.length();i++)
{
if(s[i]<='Z'&&s[i]>='A')
{
s[i]=s[i]-'A'+'a';
}
}
return s;
}
};
class Solution {
public:
int lengthOfLastWord(string s) {
int count = 0, i = s.length()-1;
while(i>=0&&s[i]==' ')
{
i--;
}
for(i;i>=0;i--)
{
if(s[i]!=' ')
{
count++;
}
else
break;
}
return count;
}
};
class Solution {
public:
int numJewelsInStones(string jewels, string stones)
{
int count=0;
for(int i=0;i<stones.length();i++)
{
for(int j=0;j<jewels.length();j++)
{
if(stones[i]==jewels[j])
{
count++;
}
}
}
return count;
}
};
class Solution {
public:
map<char, int> Map;
int numJewelsInStones(string jewels, string stones) {
int count=0;
for(int i=0; i<jewels.length();i++){
Map[jewels[i]]=1;
}
for(int i=0; i<stones.length();i++){
if(Map.count(stones[i])==1){
count++;
}
}
return count;
}
};
-建立一个hash 键是char 值是频率 看他出现了几次,第一个循环就拉构建这个hash 第二个循环用来判断
class Solution {
public:
int firstUniqChar(string s) {
unordered_map<char, int> frequency;
for (int i=0;i<s.length();i++) {
frequency[s[i]]++;
}
for (int i = 0; i < s.length(); i++) {
if (frequency[s[i]] == 1)//他的值只有一个
{
return i;
}
}//循环结束都没有那就返回-1了
return -1;
}
};
题解
class Solution {
public:
/* 辅助函数:
- 返回整数是否超过整数范围
*/
bool tooLarge(long long res) {
return res >= INT_MAX || res <= INT_MIN;
}
/*
函数功能: 输入字符串, 输出对应的整数.
逻辑:
1. 去除前置空格
2. 检查下一个字符是正还是负, 若两者都不存在, 假设为正.
3. 往后读字符, 直到到达非数字字符, 或是到达字符串的末尾.
4. 把读入的字符转为带符号整数
5. 如果整数超过整数范围, 做截断.
*/
int myAtoi(string s) {
int i = 0;
int len = s.length();
if (len == 0) return 0;
// 1. 去除前置空格
while (i < len && s[i] == ' ')++i;
// 2. 检查下一个字符是正还是负, 若两者都不存在, 假设为正
if(isdigit(s[i]) == false && s[i] != '-' && s[i] != '+') return 0;
int poitiveSign = (s[i] != '-') ? 1 : -1;
if (isdigit(s[i]) == false) ++i;
// 3. 往后读字符, 直到到达非数字字符, 或是到达字符串的末尾.
long long res = 0;
bool beginPos = true;
while (i < len && isdigit(s[i])) {
int digit = s[i] - '0';
// 4. 把读入的字符转为整数
res = res * 10 + digit;
bool stop = tooLarge(res * poitiveSign);
if (stop) return poitiveSign == 1 ? INT_MAX : INT_MIN; // 数字的绝对值已经很大了, 后面的数不用再考虑.
++i;
}
return (int)(res * poitiveSign);
}
};
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if (!strs.size()) {
return "";
}
string prefix = strs[0];
int count = strs.size();
for (int i = 1; i < count; ++i) {
prefix = longestCommonPrefix(prefix, strs[i]);
if (!prefix.size()) {
break;
}
}
return prefix;
}
string longestCommonPrefix(const string& str1, const string& str2) {
int length = min(str1.size(), str2.size());
int index = 0;
while (index < length && str1[index] == str2[index]) {
++index;
}
return str1.substr(0, index);
}
};
class Solution {
public:
void reverseString(vector<char>& s) {
int n = s.size();
for (int left = 0, right = n - 1; left < right; ++left, --right) {
swap(s[left], s[right]);
}
}
};
class Solution {
public:
string reverseStr(string s, int k) {
for (int i = 0; i < s.size(); i += (2 * k)) {
// 1. 每隔 2k 个字符的前 k 个字符进行反转
// 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符
if (i + k <= s.size()) {
reverse(s.begin() + i, s.begin() + i + k );
continue;
}
// 3. 剩余字符少于 k 个,则将剩余字符全部反转。
reverse(s.begin() + i, s.begin() + s.size());
}
return s;
}
};
简单就是把字符串都截取出来 然后倒着拼接
还有一个就是上面说的 全部倒着reserve(end 表示的是最后一个字母的后一个),然后搞一个双指针(好好想想这个双指针如何设置 毕竟我们要指向一个字符串的头和尾) 逐个reserve
认真看代码的逻辑 可以学到很多
class Solution {
public:
string reverseWords(string s) {
reverse(s.begin(), s.end());//首先翻转整个列表
int n = s.size();//求一下藏毒
int k = 0;//用来向前移动的
for (int i = 0; i < n; i ++ ) //一次循环就是翻转一个单词
{
if (s[i] == ' ') continue;//如果是空格后移
int j = i;//去除出空格后 就可以开始把i复制给j了
//如果没到结尾 那就一直j 这边的j你想想是不是可以表示个数 他就是最后一个字母的后一位
while (j < n && s[j] != ' ') j++;
reverse(s.begin() + i, s.begin() + j);//翻转 是j不是j-1 因为要表示最后有一个字母的后一个
while( i < j) s[k++] = s[i++];//(易错)开始前移动 这边很巧妙 并且是i
if (k != 0) s[k ++ ] = ' ';//用来填充一个空格 一开始不用填充,移动后 马上填充一个空格
i = j;//修改初始点
}
s.erase(s.begin() + k-1, s.end());//删除后面的空格
return s;
}
};
class Solution {
public:
string reverseWords(string s) {
//reverse(s.begin(), s.end());//首先翻转整个列表
int n = s.size();//求一下藏毒
int k = 0;//用来向前移动的
for (int i = 0; i < n; i ++ ) //一次循环就是翻转一个单词
{
if (s[i] == ' ') continue;//如果是空格后移
int j = i;//去除出空格后 就可以开始把i复制给j了
//如果没到结尾 那就一直j 这边的j你想想是不是可以表示个数 他就是最后一个字母的后一位
while (j < n && s[j] != ' ') j++;
reverse(s.begin() + i, s.begin() + j);//翻转 是j不是j-1 因为要表示最后有一个字母的后一个
while( i < j) s[k++] = s[i++];//(易错)开始前移动 这边很巧妙 并且是i
if (k != 0) s[k ++ ] = ' ';//用来填充一个空格 一开始不用填充,移动后 马上填充一个空格
i = j;//修改初始点
}
s.erase(s.begin() + k-1, s.end());//删除后面的空格
return s;
}
};