【剑指Offer】11-15题(二分+DFS+BFS+DP+简单位运算)

🍉旋转数组的最小数字

剑指 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

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

广度优先搜索

借助队列实现,力扣题解有大佬画的图也十分清晰易懂。

不怎么用广度优先搜索,所以不怎么熟练,代码不会的点是对着题解解决的,相当于对着题解敲了一遍。

不过这题用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];
    }
};

这题题解有大佬用贪心做,但是贪心要用数学证明可行性.

🍉二进制中1的个数

剑指 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;
    }
};
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

喜欢乙醇的四氯化碳

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

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

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

打赏作者

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

抵扣说明:

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

余额充值