剑指offer刷题记录

gogogo!
面对恐惧最好的办法就是面对它,战胜它!奥里给!
在leetcode官网上刷

面试题51.数组中的逆序对
一开始的想法自然而然想要暴力解,想冒泡一样两层循环对比

class Solution {
public:
    int reversePairs(vector<int>& nums) {
     int count=0;
     for(int i=0;i<=nums.size()-2;++i)//从第一个序号到倒数第二个序号
     {
         for(int j=i+1;j<=nums.size()-1;++j)//从后一个位置遍历到最后,比较i跟j
          {
              if(nums[j]<nums[i])
              {
                  ++count;
              }
          }
     }
     return count;
    }
};

超时= =
官方思路:使用归并排序,阶段性统计逆序数
来日自己写

面试题03.数组中重复的数字
要求只是找出任意重复的数字,而且数字大小范围都在0-n-1,这样难度就偏低了。

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
     vector<int>book(nums.size(),0);
     for(int i=0;i<=nums.size()-1;++i)
     {
         ++book[nums[i]];
     }
     for(int i=0;i<=book.size()-1;i++)
     {
         if(book[i]>1)
         {
             return i;
         }
     }
     return 0;
    }
};

利用桶排序,不管了,下一道

面试题04.二维数组中的查找
看到这道题第一反应就是用搜索,DFS和BFS都可以,可能最近搜索看的多吧。。

class Solution {
    void DFS(vector<vector<int>>& matrix,int x,int y,int target,int &logo,vector<vector<int>> direction)
    { 
       if(x>matrix.size()-1||y>(matrix[0].size()-1))
    {
        return;
    }
    
        if(matrix[x][y]==target)
        {
            logo=1;
        }
        
        for(int i=0;i<=1;i++)//可能的两种方向
        {
            
            x=x+direction[i][0];
            y=y+direction[i][1];
            DFS(matrix,x,y,target,logo,direction);
            
        }
        return ;
    }
public:

    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
     vector<vector<int>> direction={{1,0},{0,1}};
     int logo=0;

        if(matrix.size()<1||matrix[0].size()<1)
         return false;

        DFS(matrix, 0,0,target,logo,direction);
        if(logo==1)
     {
         return true;
     }
     return false;
     
    }

};

上述代码是有错的,错误的原因是x和y是和当时状态绑定的坐标,然而我却让它成为一个随程序运行变化的坐标,应该用类似栈的结构来存这个坐标。
所以我们应该在每次的for循环之后后退一步让它返回原来的位置(相当于它此时此地就是这个状态,无数种可能性中的一环),其实还应该增加一个map记录是否曾经访问用于优化。
岛屿着色法为毛不用回退?
因为它可以一次性涂到底涂完一个岛,最后return一次就够了。而我们这里显然是不行的。
总而言之,只要是多次return找目标的,一定要回退!
修改之后:
结果应该是对的,但是超时

class Solution {
    void DFS(vector<vector<int>>& matrix,int x,int y,int target,int &logo,vector<vector<int>> direction)
    { 
       if(x>matrix.size()-1||y>(matrix[0].size()-1))
    {
        return;
    }
    
        if(matrix[x][y]==target)
        {
            logo=1;
        }
        
        for(int i=0;i<=1;i++)//可能的两种方向
        {
            
            x=x+direction[i][0];
            y=y+direction[i][1];
            if(x<=matrix.size()-1&&y<=(matrix[0].size()-1))
            {
            DFS(matrix,x,y,target,logo,direction);
            }
               x=x-direction[i][0];
               y=y-direction[i][1];
        }
        return ;
    }
public:

    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
     vector<vector<int>> direction={{1,0},{0,1}};
     int logo=0;

        if(matrix.size()<1||matrix[0].size()<1)
         return false;

        DFS(matrix, 0,0,target,logo,direction);
        if(logo==1)
     {
         return true;
     }
     return false;
     
    }

};

直接暴力解给过了,唉

class Solution {
public:
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
        if(matrix.size()<1||matrix[0].size()<1)
        return false;
         for(int i=0;i<=matrix.size()-1;++i)
         {
             for(int j=0;j<=matrix[0].size()-1;++j)
             {
                 if(matrix[i][j]==target)
                 {
                     return true;
                 }
             }
         }
     return false;
    }

};

方法2:
若数组为空,返回 false
初始化行下标为 0,列下标为二维数组的列数减 1
重复下列步骤,直到行下标或列下标超出边界
获得当前下标位置的元素 num
如果 num 和 target 相等,返回 true
如果 num 大于 target,列下标减 1
如果 num 小于 target,行下标加 1
可以证明这种方法不会错过目标值。如果当前元素大于目标值,说明当前元素的下边的所有元素都一定大于目标值,因此往下查找不可能找到目标值,往左查找可能找到目标值。如果当前元素小于目标值,说明当前元素的左边的所有元素都一定小于目标值,因此往左查找不可能找到目标值,往下查找可能找到目标值。
(若是这样左下角和右上角都是可以的)

面试题05.替换空格
leetccode要注意特殊情况

class Solution {
public:
    string replaceSpace(string s) {
        if(s=="")
        {
            return s;
        }
        string copy="";
       for(int i=0;i<=s.size()-1;++i)
       {
         if(s[i]==' ')
         {
           copy+='%';
           copy+='2';
           copy+='0';
         }
         else
         {
             copy+=s[i];
         }
       }
       return copy;
    }
};

面试题06.从尾到头打印链表
看到这个理所当然想到栈,先进后出.

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
      vector<int>stack;int top=0;
      vector<int>stackcopy;
    ListNode*p;
     p=head;
      while(p!=NULL)
      {
          stack.push_back(p->val);
           top++;
           p=p->next;
      }
      for(int j=stack.size()-1;j>=0;--j)
      {
          stackcopy.push_back(stack[j]);
         
      }
      return stackcopy;
    }
};

不过这个不算多快,也不会翻转链表。
一个利用递归的例子,利用了后调用先return:
真牛逼,记住了

class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        if(!head)
            return {};
        vector<int> a=reversePrint(head->next);
        a.push_back(head->val);
        return a;
    }
};

面试题07.重建二叉树
好像是两种遍历可以确定一棵树来着,可是不记得了,直接看解法!!
思路是这样:
前序遍历的首个元素即为根节点 root 的值;
在中序遍历中搜索根节点 root 的索引 ,可将中序遍历划分为
[ 左子树 | 根节点 | 右子树 ] 。
根据中序遍历中的左(右)子树的节点数量,可将前序遍历划分为
[ 根节点 | 左子树 | 右子树 ] 。
同样的办法继续对左右子树继续划分(递归),就OK了

划分(vector1,vector2,left,right)
{
得到root值
得到root在中序中坐标index
将root值添加进树怎么操作是个问题。。
划分(vector1,vector2,left,index-1)//划分左子树
划分(vector,index+1,right)//划分右子树
}

class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        //递归分治
        return recursionBuild(preorder.begin(),preorder.end(),inorder.begin(),inorder.end());
    }

    //递归分治
    TreeNode* recursionBuild(vector<int>::iterator preBegin, vector<int>::iterator preEnd,vector<int>::iterator inBegin, vector<int>::iterator inEnd )
    {
        if(inEnd==inBegin) return NULL;
        TreeNode* cur = new TreeNode(*preBegin);
        auto root = find(inBegin,inEnd,*preBegin);
        cur->left = recursionBuild(preBegin+1,preBegin+1+(root-inBegin),inBegin,root);
        cur->right = recursionBuild(preBegin+1+(root-inBegin),preEnd,root+1,inEnd);
        return cur;
    }
};


面试题11:旋转数组的最小数字
为毛要旋转,不能直接找最小数字?
用的是直接找,也可以用其他办法,比如二分查找优化效率,懒得看了。
整体思路是不断缩小搜索的范围。

面试题12. 矩阵中的路径
这道题一看到立马想到用深度搜索,但是不管怎么样复杂度都太高了,这次的终止条件是字符串匹配成功而不是找到某个点。这样是不是最好新定义一个字符串跟它对比。
自己的思路有点乱,就算最后做出来也是千疮百孔,看看别人的吧。
思路:
深度优先搜索: 可以理解为暴力法遍历矩阵中所有字符串可能性。DFS 通过递归,先朝一个方向搜到底,再回溯至上个节点,沿另一个方向搜索,以此类推。
剪枝: 在搜索中,遇到 这条路不可能和目标字符串匹配成功 的情况(例如:此矩阵元素和目标字符不同、此元素已被访问),则应立即返回,称之为 可行性剪枝

递归参数
当前元素在矩阵 board 中的行列索引 i 和 j ,
当前目标字符在 word 中的索引 k 。
终止条件
返回 false
false : ① 行或列索引越界 或 ② 当前矩阵元素与目标字符不同 或 ③ 当前矩阵元素已访问过 (③ 可合并至 ② ) 。
返回 true
true : 字符串 word 已全部匹配,即 k = len(word) - 1 。
递推工作:
标记当前矩阵元素: 将 board[i][j] 值暂存于变量 tmp ,并修改为字符 ‘/’ ,代表此元素已访问过,防止之后搜索时重复访问
搜索下一单元格: 朝当前元素的 上、下、左、右 四个方向开启下层递归,使用 或 连接 (代表只需一条可行路径) ,并记录结果至 res 。
还原当前矩阵元素: 将 tmp 暂存值还原至 board[i][j] 元素。
回溯返回值: 返回 res ,代表是否搜索到目标字符串。

贴一份java的版本

class Solution {
    public boolean exist(char[][] board, String word) {
        char[] words = word.toCharArray();
        for(int i = 0; i < board.length; i++) {
            for(int j = 0; j < board[0].length; j++) {
                if(dfs(board, words, i, j, 0)) return true;
            }
        }
        return false;
    }
    boolean dfs(char[][] board, char[] word, int i, int j, int k) {
        if(i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k]) return false;
        //如果等于word下一个元素就继续下一步了
        if(k == word.length - 1) return true;
        char tmp = board[i][j];
        board[i][j] = '/';
        boolean res = 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);
        board[i][j] = tmp;//回溯很重要!!除了着色法只有一次return不需要回溯,其他都需要,像这里就算每次不符合条件或者符号条件return true或者false,找一条路径的都需要回溯状态
        return res;
    }
}
//这份代码没有另外定义bool[][]以及用||符号剪枝值得学习
class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        if(board.size() == 0) return false;
        for (int i=0;i<board.size();i++){
            for(int j=0;j<board[0].size();j++){
                if (dfs(board,word,i,j,0)){
                    return true;
                }
            }
        }
        return false;
    }

    bool dfs(vector<vector<char>>& board, string& word, int i,int j,int length){
        if(i>=board.size()||j>=board[0].size()||i<0||j<0||length>=word.size()||word[length]!=board[i][j]){
            return false;
        }
        if(length==word.size()-1&&word[length]==board[i][j]){
            return true;
        }
        char temp=board[i][j];//其实每一层都存了一个temp留待之后回溯
        board[i][j]='0';//之后访问到也是不会等于字符串里的东西的
        bool flag=dfs(board,word,i,j+1,length+1)||dfs(board,word,i,j-1,length+1)||dfs(board,word,i+1,j,length+1)||dfs(board,word,i-1,j,length+1);
        board[i][j]=temp;//只有当上面return true后者false会执行这一步
        return flag;
    }
};
//相当于上一份代码C++版本

学习的点:
这个将字符设置为‘0’然后之后重置省去了bool[][]数组的空间。

这个C++版本,想法跟我基本是一样,
在board中找到一个位置,使得board[i][j] == word[0],可以作为搜索的入口
由于一个格子不能重复进入,因此需要定义一个visit数组,保证每个格子只进入一次
找到一个可行解即返回true。若该次搜索返回false,那么进行步骤1.,找到下一个可行的入口,进入下一次搜索
直到遍历完整个board,仍没有搜索到目标路径,返回false

class Solution {
public:

    vector<vector<int>> dxy = {{-1, 0}, {1, 0}, {0, 1}, {0, -1}};
    int rows, cols;
    bool dfs(vector<vector<char>>& board, vector<bool>& visit, int i, int j, string& word, int idx){
        if(board[i][j] != word[idx]) return false;
        visit[i*cols+j] = true;
        idx++;
        for(auto xy : dxy){
            int x = xy[0] + i;
            int y = xy[1] + j;
            if(x < 0 || x >= rows || y < 0 || y >= cols || visit[x*cols+y]) continue;
            else{
                if(dfs(board, visit, x, y, word, idx)) return true;
            }
        }
        visit[i*cols+j] = false;
        if(idx == word.size()) return  true;
        return false;
    }

    bool exist(vector<vector<char>>& board, string word) {
        if(word == "")  return false;
        rows = board.size();
        cols = board[0].size();
        vector<bool> visit(rows * cols, false);
        for(int i = 0;i < rows; i++){
            for(int j = 0; j < cols; j++){
                if(board[i][j] == word[0]){
                    if(dfs(board, visit, i, j, word, 0)) return true;
                }
            }
        }
        return false;
    }
};

学习的点:
定义bool只需要定义一维数组就可以

总结一下DFS要点:
1.终止搜索的条件,剪枝 return false或者是达到目的,返回ture
2.回溯,DFS之后紧跟着返回上一步的状态。

面试题13 机器人的运动范围
贴一个自己写的有问题的

class Solution {
public:
     vector<vector<int>>dir={{0,1},{1,0},{-1,0},{0,-1}};
 void DFS(int x,int y,int m,int n,int k,int&count,vector<bool>& visit)
 {   int x1=x/100;
     int x2=(x-x1*100)/10;
     int x3=x-x1*100-x2*10;
     int sumx=x1+x2+x3;
     int y1=y/100;
     int y2=(y-y1*100)/10;
     int y3=y-y1*100-y2*10;
     int sumy=y1+y2+y3;
    if(sumx+sumy<=k)//满足条件count++
    {
      visit[x*n+y]=true;
      count++;
    }

    for(int i=0;i<=3;i++)
    {
        x+=dir[i][0];
        y+=dir[i][1];
        
        if(x<0||y<0||x>m-1||y>n-1||visit[x*n+y]==true)
       {
           continue;
       }
            DFS(x,y,m,n,k,count,visit);  
    }
    return;
 }

    int movingCount(int m, int n, int k) {     
        vector<bool> visit(m*n,false);
        int count=1;//记录能够达到的格子
        DFS(0,0,m,n,k,count,visit);
        return count;
    }
};

一直一直都是他妈的overflow

网友的

class Solution {
public:
    int s=0;
    int movingCount(int m, int n, int k) {
        vector<vector<int>>v(m,vector<int>(n));
        dfs(0,0,m,n,k,v);
        return s;
    }
    void dfs(int x,int y,int m,int n,int k,vector<vector<int>>& v){
        if(x>=m||y>=n||x<0||y<0||x/10+x%10+y/10+y%10>k||v[x][y]==1)return ;
        s++;
        v[x][y]=1;
        dfs(x+1,y,m,n,k,v);
        dfs(x,y+1,m,n,k,v);
    }
};
//

你可能感兴趣的:(offer冲冲冲)