剑指 Offer 11. 旋转数组的最小数字 - 力扣(LeetCode)
大意:在 3 4 5 0 1 2这种数组里找出最小的数字。
类似的数组还有[1 2 3 4 5];
[2,2,2,2,2];
[6,9,11,1,2]
第一反应,找最小值,直接遍历数组不就行了。。。(剑指Offer上会有这么简单的题目吗)
之后看了题解,思想是二分。可以把这种数组看做一种结构,这种结构可以分为左右数组。
以[6,9,11,1,2]为例,分为左右数组,左数组为6,9,11;右数组为1,2。我们要找的就是右数组中的第一个数。
可以看出左数组任意一个值都大于右数组,右数组中最右边的又是最大的值,所以左数组任意一个值都大于右数组最右边的值。
由此我们可以用二分,记左右两边为left和right,mid=left+(right-left)/2。
mid=(left+right)/2;和mid=left+(right-left)/2相比,显然前一种情况更容易溢出
num[right]就是右数组最右边的值。
当num[mid]< num[right],因为左数组任意一个数都会大于右数组,所以mid肯定在右数组中。此时mid右边全是右数组的值,所以right=mid。
当num[mid]>num[right],同理mid肯定在左数组中,所以mid左边的值肯定要舍掉,因为我们要找右数组的第一个。即left=mid+1.(注意:left=mid的写法可能导致死循环)
当num[mid]==num[right]时,right–,为什么?此时我们不能判断最小值是在左数组还是右数组,比如[1,0,1,1,1]和[1,1,1,0,1]这两个数组在符合当前条件下时最小值一个在左数组,一个在右数组。此时我们的解决办法是把right–。
right–。因为 在符合条件left<right时有mid!=right,我们去掉了num[right],与它相等的num[mid]仍然在二分区间内。所以如果num[right]是我们要找的值,二分区间内也有一个和他相等的数替代它。
class Solution {
public:
int minArray(vector<int>& numbers) {
int left=0;
int right=numbers.size()-1;
while(left<right)
{
int mid=left+(right-left)/2;
if(numbers[mid]<numbers[right])//mid在右区间内
{
right=mid;
}
else if(numbers[mid]>numbers[right])//mid在左区间内
{
left=mid+1;
}
else
{
right--;
}
}
return numbers[left];
}
};
剑指 Offer 12. 矩阵中的路径 - 力扣(LeetCode)
典型的dfs迷宫问题。四个方向去走,找到一条合理的出路即可。
class Solution {
public:
bool exist(vector<vector<char>>& board, string word) {
Init(board);
for(int i=0;i<row;i++)
{
for(int j=0;j<col;j++)
{
bool ret=dfs(board,word,i,j,0);//没有规定起点是(0,0)
if(ret)
{
return ret;
}
}
}
return false;
}
private:
vector<vector<bool>> visit;
int col,row;
void Init(vector<vector<char>>& board)
{
row=board.size();//行
col=board[0].size();//列
visit.resize(row,vector<bool>(col)); //初始化visit
}
bool dfs(vector<vector<char>>& board, string& word,int i,int j,int k)
{
if(i>=row||i<0||j>=col||j<0||board[i][j]!=word[k]||visit[i][j])//数组越界,字母不相等,已经访问过都返回false
{
return false;
}
if(k==word.size()-1)//找完了,返回true
{
return true;
}
visit[i][j]=1;//标记走过的位置
bool ret=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);//四个方向去走
if(ret==false) visit[i][j]=0;//走进死胡同了,回退(回溯),即取消标记的位置
return ret;
}
};
剑指 Offer 13. 机器人的运动范围 - 力扣(LeetCode)
DFS,,但是只要两个方向。
判断为什么只需要两个方向,画图可知机器人可以到达的点呈三角形分布,则可以一直往下走,走到三角形的最下面再回溯往右走。可以看看力扣里大佬画的图,清晰明了。
class Solution {
public:
int movingCount(int m, int n, int k) {
visit.resize(m,vector<bool>(n));
return dfs(m,n,k,0,0);;
}
private:
vector<vector<bool>>visit;
int cnt=0;
int dfs(int m,int n,int k,int i,int j)
{
if(i==100) i=1;
if(j==100) j=1;//题目给的数据范围里有100,对其进行特殊处理,但是这里只是因为是边界才这么处理,如果有101,102这么做就不行了。
int ans=i%10+i/10+j%10+j/10;
if(i<0||i>=m||j<0||j>=n||ans>k||visit[i][j])
{
return 0;
}
visit[i][j]=1;//标记访问
return 1+dfs(m,n,k,i+1,j)+dfs(m,n,k,i,j+1);//先往下一直走然后回溯往右走
}
};
广度优先搜索
借助队列实现,力扣题解有大佬画的图也十分清晰易懂。
不怎么用广度优先搜索,所以不怎么熟练,代码不会的点是对着题解解决的,相当于对着题解敲了一遍。
不过这题用BFS好像慢很多。
class Solution {
public:
int movingCount(int m, int n, int k) {
vector<vector<bool>>visit(m,vector<bool>(n));
int cnt=0;
queue<vector<int>>q;
q.push({0,0,0,0});//i,j,si,sy
while(!q.empty())
{
vector<int> tmp=q.front();
q.pop();
int i=tmp[0],j=tmp[1];
int si=tmp[2],sj=tmp[3];
if(i>=m||j>=n||si+sj>k||visit[i][j])
{
continue;
}
visit[i][j]=1;
cnt++;
q.push({i+1,j,(i+1)%10==0?si-8:si+1,sj});
q.push({i,j+1,si,(j+1)%10==0?sj-8:sj+1});
}
return cnt;
}
};
剑指 Offer 14- I. 剪绳子 - 力扣(LeetCode)
dp,总感觉dp像找规律。
记f(n)为绳子长为n时最大值,找到的公式如下
f(n)=max( f(i)*(n-i),f(i)*f(n-i))
自己列表比较n-i和f(n-i)发现,当n>=4时,始终有f(n-i)>(n-i)。所以我们再自己列出前三个数字就能进行递推了。
由上面可知当n>=4时,f(n)=f(i)*f(n-i)。
class Solution {
public:
int cuttingRope(int n) {
vector<int>dp(60);
if(n==2) return 1;
if(n==3) return 2;
dp[1]=1;
dp[2]=2;
dp[3]=3;//前面这三个数字是为了后面的递推准备的
for(int i=4;i<=n;i++)
{
//算dp[i]
int ans=-1;
for(int j=1;j<=i/2;j++)
{
ans=max(ans,dp[j]*dp[i-j]);
}
dp[i]=ans;
}
return dp[n];
}
};
这题题解有大佬用贪心做,但是贪心要用数学证明可行性.
剑指 Offer 15. 二进制中1的个数 - 力扣(LeetCode)
简单位运算
class Solution {
public:
int hammingWeight(uint32_t n) {
int cnt=0;
for(int i=0;i<32;i++)
{
if((n>>i)&1)
{
cnt++;
}
}
return cnt;
}
};
一个数减1再&自己会把自己最右边的1变成0,操作几次相当于有几个1。
class Solution {
public:
int hammingWeight(uint32_t n) {
int cnt=0;
while(n)
{
n=(n-1)&n;//会把这个数最右边的1变成0
cnt++;
}
return cnt;
}
};