《剑指offer》刷题笔记(1)

文章目录

  • 数组去重并返回第一个重复的数字(数组范围0~n-1)
    • 数组去重-改(不修改数组找出重复数字)
  • 二维数组中的查找
  • 替换空格
  • 从尾到头打印链表
  • 重建二叉树
  • 二叉树的下一个节点
  • 两个栈实现一个队列,实现pop()和push操作
  • Fibonacci及其变形
    • Fibonacci数列
    • 普通版青蛙跳台阶
    • 变态版青蛙跳台阶
    • 矩形覆盖
  • 旋转数组最小数字
  • 矩阵中的路径
  • 机器人运动轨迹
  • 剪绳子
  • 二进制中1的个数
  • 快速幂
  • 打印从1到最大的n位数
  • 链表节点删除
    • O(1)时间删除链表的节点P
    • 删除链表中的重复节点
  • 正则表达式匹配

数组去重并返回第一个重复的数字(数组范围0~n-1)

public class Duplication{
    boolean duplication(int[] numbers,int [] num)
    {
        for(int i = 0;i<numbers.length;i++)
                if(numbers[i]<0 || numbers[i]>numbers.length-1)
                return false;
            for(int j=0;j<numbers.length;j++)
            {
                while(numbers[j]!=j)
                {
                    if(numbers[j]==numbers[numbers[j]])pp
                    {
                        num[0] = numbers[j];
                        return true;
                    }
                    int temp = numbers[j];
                    numbers[j] = numbers[temp];
                    numbers[temp]=temp;
                }
            }
            throw new IllegalArgumentException("No such sum solution");
        }
    }

数组去重-改(不修改数组找出重复数字)

不修改原有数组,可以采用辅助数组的方法,此时空间复杂度是n,如果不使用额外的空间,可以使用以下的类似二分查找的方法

此算法时间复杂度是nlogn

public class getDuplication {
    int duplication(int[] numbers)
    {
        int left = 1;
        int right = numbers.length-1;
        while(right >= left)
        {
            int middle = (right - left)/2 + left;
            //二分查找midlle确定使用减的方法,如果使用加的方法会死循环
            int count = countRange(numbers,left,middle);
            if(right == left)
                if (count > 1)
                    return left;
                else
                    break;
            if(count > middle-left+1)
                right = middle;
            else
                left = middle + 1;
        }
        return  -1;
    }
 int countRange(int[] a , int left ,int right )
    {
        if(a == null)
            return -1;
        int count = 0;
        for(int i=0; i<a.length;i++)
            if(a[i] >=left && a[i]<=right)
                count ++;
        return count;
    }
    }

二维数组中的查找

采用不断矩阵右上角查找,并每次剔除一行或者一列的方法

查找到的情况放在第一个判断条件里面

public class Findum {
    public int[] index(int[][] matrix,int row, int column,int target)
    {
         int i = 0;
         int j = column-1;
         while( i < row && j >= 0){
               if(target == matrix[i][j])
                 return new int[] {i, j};
             else
             {
                 if(target > matrix[i][j])
                     i++;
                 else if(target < matrix[i][j])
                 j--;
             }
         }
        throw new IllegalArgumentException("No such sum solution");
    }
}

替换空格

题目:替换空格,实现一个函数,把字符串中的每个空格替换成"%20"。例如,输入"we are happy.",输出"we%20are%20happy."

使用双指针,p指向原字符串组后一个元素,q指向扩充后字符串最后一个元素,逐个倒退赋值,p如果遇到空格,p往前移动一位,q进行填充,如此循环,直到p和q相等

public class Replace
{
    public String ReplaceBlank(String  str)
    {
        if(str.length()==0)
            return str;
        StringBuffer newstr = new StringBuffer(str);
        int count = getBlankNum(str);
        int p=newstr.length()-1;
        for(int i=0;i<count*2;i++)
        {
            newstr.append("0");
        }
        int q=newstr.length()-1;
        while(p!=q)
        {
            while(((Character)newstr.charAt(p)).equals(' '))
            {
                p--;
                newstr.replace(q-2,q+1,"%20");
                q=q-3;
                if(p<0) return newstr.toString();
            }
            newstr.setCharAt(q,newstr.charAt(p));
            p--;
            q--;
        }
        return newstr.toString();
    }
    int getBlankNum(String str)
    {
        int count=0;
        for(int i=0;i<str.length();i++)
        {
            if(str.charAt(i)==' ')
                ++count;
        }
        return  count ;
    }
}

采用c风格的字符数组,可以化简

申请新的数组,使用append()方法,从前往后复制,遇到空格添加%20为更简单的方法

从尾到头打印链表

题目:输入一个链表头节点,从尾到头反过来打印出每个节点的值

使用额外的栈来存储结点,或者使用递归

//使用栈
class ListSolution1 {
    public void  reverseNode(ListNode head) {
        if(head.next == null)
            throw new IllegalArgumentException("Don't have any listnode");
        int [] myStack = new int[50];
        int StackSize = -1;
        ListNode p = head.next;
        while (p!= null)
        {
            StackSize++;
            myStack[StackSize] = p.val;
            p=p.next;
        }
        while(StackSize!=-1)
            System.out.println(myStack[StackSize--]);
    }
}
//递归调用
class ListSolution2 {
    public void  reverseNode(ListNode head) {
        if(head.next == null)
            throw new IllegalArgumentException("Don't have any listnode");
        ListNode p = head.next;
        Reverse(p);
    }
    public void Reverse(ListNode p)
    {
        if(p!=null)
        {
            Reverse(p.next);
            System.out.println(p.val);
        }
    }
}

重建二叉树

题目:输入某二叉树的前序遍历和中序遍历结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

#include
#include
#include
using std::vector;

class ListSolution{
public:
    TreeNode* reConstructBinaryTree(vector<int>pre,vector<int>vin)
        {
        if(pre.empty() && vin.empty())
            return nullptr;
        return Treecore(pre.begin(),pre.end(),vin.begin(),vin.end());
    }
    TreeNode* Treecore(vector<int>::iterator startPre,vector<int>::iterator endPre,
                       vector<int>::iterator startIn,vector<int>::iterator endIn)
                       {

                            int rootValue = startPre[0];
                            auto root = new TreeNode(rootValue);

                            if(startPre==endPre) {
                                if (startIn == endIn)
                                    return root;
                                else
                                    throw std::exception();
                            }

                            auto rootIn = startIn;
                            while (*rootIn != rootValue)
                                ++rootIn;
                            if(rootIn > endIn)
                            {
                                throw std::exception();
                            }
                            int leftLength = rootIn - startIn;
                            auto leftPreEnd = startPre+leftLength;
                            if(leftLength>0)
                            {
                                root->left = Treecore(startPre+1,leftPreEnd,startIn,rootIn-1);
                            }
                            if(rootIn < endIn)
                            {
                                root->right = Treecore(leftPreEnd+1,endPre,rootIn+1,endIn);
                            }
                           return root;
                       }
};
public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        if(pre == null || in == null || pre.length == 0 || in.length == 0){
            return null;
        }
        return buildTree(pre, in, 0, pre.length - 1, 0, in.length - 1);
    }
    public TreeNode buildTree(int[] pre, int[] in, int preStart, int preEnd, int inStart, int inEnd){
        TreeNode root = new TreeNode(pre[preStart]);
        int rootIn = 0;
        for(; rootIn < in.length; rootIn++){
            if(in[rootIn] == root.val){
                break;
            }
        }
        int leftLength = rootIn - inStart;
        int rightLength = inEnd - rootIn;
        if(leftLength > 0){
            root.left = buildTree(pre, in, preStart + 1, preStart + leftLength, inStart, rootIn - 1);           
        }
        if(rightLength > 0){
            root.right = buildTree(pre, in, preStart + leftLength + 1, preEnd, rootIn + 1, inEnd);   
        }
         
        return root;
    }
}

二叉树的下一个节点

给定一颗二叉树和其中的一个节点,如何找出中序遍历序列的下一个节点?树中的节点除了有两个分别指向左、右节点的指针,还有一个指向父节点的指针

class solution
{
public:
    TreeLinkNode* getNextNode(TreeLinkNode* pNode)
    {
        if(pNode == nullptr)
            return nullptr;

        if(pNode->right != nullptr)
        {
            return  pNode->right;
        }

        if(pNode->next != nullptr && pNode->next->left == pNode)
            return pNode->next;
        while (pNode->next->left != pNode)
        {
            if(pNode->next->left == pNode)
                return  pNode->next;
            pNode=pNode->next;
        }
    }
};

两个栈实现一个队列,实现pop()和push操作

注意stack2为空栈的情况

public class Solution1 {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();

    public void push(int node) {
    stack1.push(node);
    }

    public int pop()
    {
        if(stack2.isEmpty()){
            if(stack1.isEmpty())
                return -1;
            while (!stack1.isEmpty()) {
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }
}

Fibonacci及其变形

Fibonacci数列

O(n)解:从下往上计算

class Solution:
    
    def Fibonacci(self, n):

        result = [0,1]

        if n<2:
            return result[n]
        fibOne = 1
        fibTwo = 0
        fibN = 0
        for i in range(2, n+1):
            fibN = fibOne + fibTwo
            fibTwo = fibOne
            fibOne = fibN

        return fibN

递归解(不推荐)

class Solution:
    def Fibonacci(self, number):
        if number < 2:
            return number
        if number >= 2:
            return self.Fibonacci(number-1) + self.Fibonacci(number-2)

普通版青蛙跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)

class Solution:
    def jumpFloor(self, number):
        # write code here
        mylist = [0, 1, 2, 3]

        if 0 < number <= 3:
            return mylist[number]
        n = 4
        while n <= number:
            mylist.append(mylist[n-1] + mylist[n-2])
            n = n + 1

        return mylist[number]

变态版青蛙跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

简单的数学归纳即可解

#python版本
class Solution:
    def jumpFloorII(self, number):
        return 2 ** (number-1)
//java版本
//位运算
public class Solution {
    public int JumpFloorII(int target) {
         return 1 << (target-1);
    }
}

矩形覆盖

我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

归纳推理,解法与青蛙跳台阶普通版相同

旋转数组最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0

class Solution:
    def minNumberInRotateArray(self, rotateArray):
        low = 0
        high = len(rotateArray) - 1
        while low < high:
            mid = low + (high - low) // 2
            if rotateArray[mid] > rotateArray[high]:
                low = mid + 1
            if rotateArray[mid] == rotateArray[high]:
                high = high - 1
            if rotateArray[mid] < rotateArray[high]:
                high = mid
        return rotateArray[low]

矩阵中的路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。 例如 a b c e s f c s a d e e 这样的3 X 4 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子.

public class Solution {

    public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
        if(matrix==null || matrix.length==0 || str==null || str.length==0 || matrix.length!=rows*cols || rows<=0 || cols<=0 || rows*cols < str.length) {
            return false ;
        }

        boolean[] visited = new boolean[rows*cols] ;
        int[] pathLength = {0} ;

        for(int i=0 ; i<=rows-1 ; i++) {
            for(int j=0 ; j<=cols-1 ; j++) {
                if(hasPathCore(matrix, rows, cols, str, i, j, visited, pathLength)) { return true ; }
            }
        }

        return false ;
    }

    public boolean hasPathCore(char[] matrix, int rows, int cols, char[] str, int row, int col, boolean[] visited, int[] pathLength) {
        boolean flag = false ;

        if(row>=0 && row<rows && col>=0 && col<cols && !visited[row*cols+col] && matrix[row*cols+col]==str[pathLength[0]]) {
            pathLength[0]++ ;
            visited[row*cols+col] = true ;
            if(pathLength[0]==str.length) { return true ; }
            flag = hasPathCore(matrix, rows, cols, str, row, col+1, visited, pathLength)  ||
                    hasPathCore(matrix, rows, cols, str, row+1, col, visited, pathLength)  ||
                    hasPathCore(matrix, rows, cols, str, row, col-1, visited, pathLength)  ||
                    hasPathCore(matrix, rows, cols, str, row-1, col, visited, pathLength) ;

            if(!flag) {
                pathLength[0]-- ;
                visited[row*cols+col] = false ;
            }
        }

        return flag ;
    }

}
  • 第二次提交
class Solution{
public:
    bool hasPath(vector<vector<char>>& matrix, string str){
        for(int i = 0;i< matrix.size(); i++)
            for(int j = 0; j< matrix[i].size();j++)
                if(dfs(matrix, str,0,ij))
                    return true;
        return false;
    }
    bool dfs(vector<vector<char>>& matrix,string &str,int u,int x, int y)
    {
        if(u == str.size())return true;
        if(matrix[x][y] != str[u])return false;
        int dx[4] = {-1,0,1,0}, dy = {0,1,0,-1};
        char t = matrix[x][y];
        matrix[x][y] = '*';
        for(int i = 0; i<4;i++){
            int a = x + dx[i],b = y + dy[i];
            if(a >= 0 && a < matrix.size() && b >= 0 && b < matrix[a].size())
            {
                if(dfs(matrix,str,u + 1,a,b))return true;
            }
        }
        matrix[x][y] = t;]
        return false;
    }
}

机器人运动轨迹

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

#include
#include
const int MAXN=100;
int dx[]={0,1,0,-1},dy[]={1,0,-1,0};    //四个方向
int vis[MAXN][MAXN]={0};    //记录数组
int sum;    //记录结果

class Solution{
public:
    void dfs(int x,int y,int k,int m,int n)
    {
        vis[x][y]=1;
        for(int i=0;i<=3;++i)
        {
            int newx=x+dx[i],newy=y+dy[i];
            //判断方格是否合法,合法就从该方格接着搜索
            if(vis[newx][newy]==0 && newx>=0 && newy>=0 && newx<m && newy<n && (newx/10+newx%10+newy/10+newy%10 <=k ))
            {
                ++sum;
                dfs(newx,newy,k,m,n);
            }
        }
    }
    int movingCount(int threshold, int rows, int cols)
    {
        if(threshold<0)
            return 0;
        memset(vis,0, sizeof(vis));
        sum=1;
        dfs(0,0,threshold,rows,cols);
        return sum;
    }
};

剪绳子

class Solution
{
public:
    int cutRope(int length) {
        if (length < 2)
            return 0;
        if (length < 4)
            return length - 1;
        int timesOf3 = length / 3;
        if (length % timesOf3 == 1) {
            --timesOf3;
        }
        int timesOf2 = (length - timesOf3 * 3) / 2;
        return (int) (pow(3, timesOf3) * pow(2, timesOf2));
    }
};

二进制中1的个数

思路:把一个整数减去1之后再和原来的整数做位与运算,得到的结果相当于把整数的二进制表示中最右边的1变成0。很多二进制的问题都可以使用这种思路

class Solution {
public:
     int  NumberOf1(int n) {
        int count = 0;
         while(n)
         {
             ++count;
             n = (n-1)&n;
         }
         return count;
     }
};

快速幂

普通方法只需控制好pow为负数,n为负数的情况。使用快速幂可以再logn的时间内完成

class Solution {
public:
   double FastPow(int n ,int pow)
{
   int res = 1;
   while(n>0)
   {
       if(pow&1)	//相当于判断pow%2==1
          res*=n;
       n*=n;
       pow>>=1;
   }
   return res;
}
   double Power(double base, int exponent) {
       if(base==0)
           return 0;
       if(exponent==1)
           return base;
       if(exponent==0)
           return 1;
       if(exponent>0)
           return FastPow(base,exponent);
       if(exponent<0)
           return 1/FastPow(base,-exponent);
}
};

打印从1到最大的n位数

使用字符串(数组)来避免大整数

为了避免代码的冗长,可以使用全排列

// 解法1,使用字符数组
public class Solution {
    public void print1ToMax(int n)
    {
        if (n > 0)
        {
            char[] chars = new char[n];
            for(int i = 0; i < n; ++i)
            {
                chars[i]='0';
            }
            while(increment(chars))
            {
                printNum(chars);
            }
        }

    }
    // 打印数字,排除掉字符数组前几位为0的情况
    public void printNum(char[] number)
    {
        int i = 0;
        while(number[i] == '0')
        {
            i++;
        }
        StringBuilder sb = new StringBuilder();
        for(int j = i; j < number.length;++j)
        {
            sb.append(number[j]);
        }
        System.out.println(sb);
    }
    // 实现了在O(1)时内判断是不是已经达到了最大的n位数
    public boolean increment(char[] chars)
    {
        boolean flag = true;
        int carry = 1;
        for(int i = chars.length - 1; i >= 0;--i)
        {
            int num = chars[i] - '0' + carry;
            if(num>9)
            {
                if(i==0)
                {
                    flag = false;
                    break;
                }
                chars[i] = '0';
            }
            else
            {
                ++chars[i];
                break;
            }
        }
        return flag;
    }
}
//使用全排列
//使用java会出现bug,这里使用cpp
class permutation
{
public:
    void permutation_core(int n)
    {
        char *chars = new char[n+1];
        memset(chars,'0',n);
        chars[n+1]='\0';
        for(int i=0;i<10;++i)
        {
            chars[0]=i+'0';
            permutation_recursion(chars,n,0);
        }
        delete[] chars;
    }
    void permutation_recursion(char *chars,int length,int index)
    {
        if(index==length-1)
        {
            print_chars(chars,length);
            return;
        }
        for(int i=0;i<10;++i)
        {
            chars[index+1]=i+'0';
            permutation_recursion(chars,length,index+1);
        }
    }
    void print_chars(char *chars,int length)
    {
        int i=0;
        while(chars[i]=='0')
            ++i;
        if(i==length)
            return;
        for(int j=i;j<length;j++)
            printf("%c",chars[j]);
        printf("\n");
    }
};

链表节点删除

O(1)时间删除链表的节点P

注意题目说的是已知某节点,因此可以做到O(1)时间删除

public class  Solution
{
    public void deleteNode(ListNode head , ListNode p)
    {
        if(head == null && p == null)
            return;
        if(p == head.next)
        {
            head.next = null;
        }
        else if(p.next != null)
        {
            p.val = p.next.val;
            p.next = p.next.next;
        }
        else
        {
            ListNode q = head.next;
            while(q.next != p)
                q  = q.next;
            q.next = null;
        }
    }
}

删除链表中的重复节点

非递归实现

public class Solution
{
    public ListNode deleteDuplication(ListNode pHead)
    {
        ListNode preNode = null;
        ListNode curNode = pHead;
        while (curNode != null)
        {
            if(curNode.next != null && curNode.next.val == curNode.val)
            {
                int val = curNode.val;
                while(curNode.next !=null && curNode.next.val == val)
                {
                    curNode = curNode.next;
                }
                if(preNode == null)  	//包括头节点在内的节点数值相同
                {
                    pHead = curNode.next;
                }
                else
                    preNode.next = curNode.next;
            }
            else
            {
                preNode = curNode;
            }
            curNode = curNode.next;		//循环进行条件,先写
        }
        return pHead;	
    }
}

递归实现

# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def deleteDuplication(self, pHead):
        # 只有0个或者一个节点,返回
        if pHead is None or pHead.next is None:
            return pHead
        Node = pHead.next
        # 当前节点是重复节点
        if Node.val == pHead.val:
            while pHead.val == Node.val and Node.next is not None:
                Node = Node.next

            # 如果当前节点与head节点值不同,则继续递归
            if pHead.val != Node.val:
                pHead = self.deleteDuplication(Node)
            # 否则,包括头节点在内的所有节点的值相同
            else:
                return None
        else:
            pHead.next = self.deleteDuplication(pHead.next)
        return pHead

正则表达式匹配

当模式中的第二个字符是“*”时

1、如果字符串第一个字符和模式中的第一个字符相匹配,那么字符串和模式都后移一个字符,然后匹配剩余的。

2、如果 字符串第一个字符和模式中的第一个字符相不匹配,直接返回false。

而当模式中的第二个字符是“*”时:

如果字符串第一个字符跟模式第一个字符不匹配,则模式后移2个字符,继续匹配。如果字符串第一个字符跟模式第一个字符匹配,可以有3种匹配方式:

1、模式后移2字符,相当于x*被忽略;

2、字符串后移1字符,模式后移2字符;

3、字符串后移1字符,模式不变,即继续匹配字符下一位,因为*可以匹配多位;

class Solution {
public:
    bool match(char* str, char* pattern)
    {
        if(!str || !pattern)
            return false;
        if(*str=='\0' && *pattern=='\0')
            return true;
        if(*str!='\0' && *pattern=='\0')
            return false;
        if(*(pattern+1)=='*')
        {
            if(*pattern==*str || (*pattern=='.'&&*str!='\0'))
                //move on the next state
                return match(str+1,pattern+2)
                //stay on the current state
                || match(str,pattern+2)
                || match(str+1,pattern);
            else
                //ignore a'*'
                return match(str,pattern+2);
       }
        if(*str==*pattern||(*pattern=='.'&&*str!='\0')) //下一个不是‘*’
            return match(str+1,pattern+1);
        return false;
    }
};

3、字符串后移1字符,模式不变,即继续匹配字符下一位,因为*可以匹配多位;

class Solution {
public:
    bool match(char* str, char* pattern)
    {
        if(!str || !pattern)
            return false;
        if(*str=='\0' && *pattern=='\0')
            return true;
        if(*str!='\0' && *pattern=='\0')
            return false;
        if(*(pattern+1)=='*')
        {
            if(*pattern==*str || (*pattern=='.'&&*str!='\0'))
                //move on the next state
                return match(str+1,pattern+2)
                //stay on the current state
                || match(str,pattern+2)
                || match(str+1,pattern);
            else
                //ignore a'*'
                return match(str,pattern+2);
       }
        if(*str==*pattern||(*pattern=='.'&&*str!='\0')) //下一个不是‘*’
            return match(str+1,pattern+1);
        return false;
    }
};

你可能感兴趣的:(数据结构和算法,剑指offer)