2021-11-28 剑指offer08,11,12,13

剑指offer08. 二叉树得到下一个节点

这题leetcode上没有找到,不过分析过程是值得学习的。

纯分情况讨论题,要求十分细致完整(其实很难想到qaq 需要画不少图分析):

  1. 一个节点有右子树,它的下一个节点是右子树中的最左节点。
  2. 没有右子树:如果该节点为左子节点,那么下一跳就是它的parent,如果是右子节点,要一直向parent查找,直到它成为parent左树的一员

剑指 Offer 11. 旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组
[3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

部分有序-> 二分法
num[mid]>num[right] 右半部分有最小值
num[mid]<num[right] (mid在内的左半部分有最小值。。。)
否则–线性查找

注意:一定要跟右边的值作比较。虽然与左边的值作比较能够确定哪个区间有序,但在无法确定最小元素在左还是在右区间。
参考leetcodeK神的分析:

补充思考: 为什么本题二分法不用 nums[m]nums[m] 和 nums[i]nums[i] 作比较?

二分目的是判断 mm 在哪个排序数组中,从而缩小区间。而在 nums[m] > nums[i]nums[m]>nums[i]情况下,无法判断 mm 在哪个排序数组中。本质上是由于 jj 初始值肯定在右排序数组中; ii 初始值无法确定在哪个排序数组中。举例如下:

对于以下两示例,当 i = 0, j = 4, m = 2i=0,j=4,m=2 时,有 nums[m] > nums[i] ,而结果不同。
[1, 2, 3, 4 ,5][1,2,3,4,5] 旋转点 x = 0x=0 : mm 在右排序数组(此示例只有右排序数组);
[3, 4, 5, 1 ,2][3,4,5,1,2] 旋转点 x = 3x=3 : mm 在左排序数组。


所以说最坏情况O(n),平均情况O(log n)

class Solution {
public:
    int minArray(vector<int>& numbers) {
        int i = 0, j = numbers.size() - 1;
        while (i < j) {
            int m = (i + j) / 2;
            if (numbers[m] > numbers[j]) i = m + 1;
            else if (numbers[m] < numbers[j]) j = m;
            else { //直接线性查找即可(或者right--,而不是left++)
                int x = i;
                for(int k = i + 1; k < j; k++) {
                    if(numbers[k] < numbers[x]) x = k;
                }
                return numbers[x];
            }
        }
        return numbers[i];
    }
};

剑指 Offer 12. 矩阵中的路径

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

例如,在下面的 3×4 的矩阵中包含单词 “ABCCED”(单词中的字母已标出)。

class Solution {
public:
    int vis[201][201]={false};
    int dx[4]={1,-1,0,0};
    int dy[4]={0,0,1,-1};
    bool res=false;


    bool exist(vector<vector<char>>& board, string word) {
        int n=board.size(),m=board[0].size();
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
                if(board[i][j]==word[0])
                {
                    dfs(i,j,0,board,word);
                    if(res) return res;

                }

            }
        }

        return false;
    }

    void dfs(int x,int y,int index,vector<vector<char>>& board, string word)
    {
        int n=board.size(),m=board[0].size();
        if(index==word.length()||res)
        {
            res=true;
            return;
        }

        if(x>=0&&x<n&&y>=0&&y<m&&vis[x][y]==false&&board[x][y]==word[index])
        {
            vis[x][y]=true;

            for(int i=0;i<4;i++)
            {
                int tx=x+dx[i];
                int ty=y+dy[i];
                dfs(tx,ty,index+1,board,word);

            }
            vis[x][y]=false;
        }
        else return;



        
    }
};

虽然感觉写的挺好,但是还是只超过5%。
if(index==word.length()||res) 增加条件,要剪枝,否则超时。。。

剑指 Offer 13. 机器人的运动范围

地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?

class Solution {
public:
    int dx[2]={0,1};
    int dy[2]={1,0};
    int maxCount=0;
    bool vis[101][101]={false};
    int movingCount(int m, int n, int k) {

        dfs(0,0,m,n,k);
        return maxCount;



    }


    void dfs(int x,int y,int m,int n,int k)
    {
        int numx=getCount(x);
        int numy=getCount(y);
        if(x<0||x>=m||y<0||y>=n||vis[x][y]==true||numx+numy>k) return;
        vis[x][y]=true;
        maxCount++;

            for(int i=0;i<2;i++)
            {
                int tx=x+dx[i];
                int ty=y+dy[i];
                dfs(tx,ty,m,n,k);
            }

    }


    int getCount(int n)
    {   
        int s=0;
        while(n!=0)
        {
            s+=n%10;
            n/=10;
        }
        return s;
    }
};

此题不是寻找一条路径——所以不需要回溯,vis数组记录是否已经计数这个格子

上一篇:79. 单词搜索


下一篇:C语言井字棋练习