剑指offer刷题

0.说明

刷题备用

目录

0.说明

1.二维数组查找

2.字符串中特定字符替代

3.将链表的值反转存入一个array中

4.重建二叉树

5.用堆栈实现pop,push

6.查找一旋转数列的最小值(二分)

7.斐波那契额数列

8.青蛙跳台阶

9.变态青蛙跳

10. 填充格子

11.统计二进制中1的个数

12.实现幂乘(快速幂)

13.整理数组中奇偶数(数组、堆栈)

14.链表倒数第k个结点

15.反转链表

 16.合成两个排序链表

 17.树的子结构

18.二叉树镜像翻转(递归)



 

1.二维数组查找

  • 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

  •  思路:
    利用二维数组由上到下,由左到右递增的规律,

    那么选取右上角或者左下角的元素a[row][col]与target进行比较,

    当target小于元素a[row][col]时,那么target必定在元素a所在行的左边,

    即col--;

    当target大于元素a[row][col]时,那么target必定在元素a所在列的下边,

    即row++;   时间复杂度 m+n

  •    bool Find(int target, vector > array) {
     
            int rowCount = array.size();
            int colCount = array[0].size();
            int i,j;
            for(i=rowCount-1,j=0;i>=0&&j         {
                if(target == array[i][j])
                    return true;
                if(target < array[i][j])
                {
                    i--;
                    continue;
                }
                if(target > array[i][j])
                {
                    j++;
                    continue;
                }
            }
            return false;
        }


2.字符串中特定字符替代

题目: 请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

思路:从前往后遍历移动次数多,从后往前每个字符只移动一次

剑指offer刷题_第1张图片



class Solution {
public:
    void replaceSpace(char *str,int length) {
        int count=0;
        for(int i=0;i=0;i--){
            if(str[i]!=' '){
                str[i+2*count]=str[i];  //‘ ’替换成‘%20’要增加3-1=2个位置
            }
            else{
                count--;
                str[i+2*count]='%';
                str[i+2*count+1]='2';
                str[i+2*count+2]='0';
            }
        }
    }
};

3.将链表的值反转存入一个array中

题目:输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。

思路:将值压入栈中,利用vector自带函数从后往前存



class Solution {
public:
    vector printListFromTailToHead(ListNode* head) {
        vector value;
        ListNode *p=NULL;
        p=head;
        stack stk;
        while(p!=NULL){
            stk.push(p->val);
            p=p->next;
        }
        while(!stk.empty()){
            value.push_back(stk.top());  //push_backs是插入尾部。从后往前存值,并放空栈
            stk.pop();
        }
        return value;
    }
};

4.重建二叉树

题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

思路:抓住根节点,递归左子树、右子树重建。

/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* reConstructBinaryTree(vector pre,vector vin) {
        int vinline=vin.size();
 
        if(vinline == 0) //line和len容易搞错
            return NULL;
        
        vector left_pre,left_vin,right_pre,right_vin;  //连续命名是不能有空格
        
        TreeNode* head = new TreeNode(pre[0]);  //创建根节点,根节点肯定是前序遍历的第一个数
        int gen = 0;
        for(int i = 0; i < vinline; i++) //找到中序遍历根节点所在位置,存放于变量gen中
        {
            if(vin[i] == pre[0])
            {
                gen = i;
                break;
            }
        }
        
        for(int i = 0; i < gen; i++)
        {
            left_vin.push_back(vin[i]);
            left_pre.push_back(pre[i+1]);//前序第一个为根节点
        }
        
        for(int i = gen+1; i < vinline; i++)
        {
            right_vin.push_back(vin[i]);
            right_pre.push_back(pre[i]);
        }
        
        //和shell排序的思想类似,取出前序和中序遍历根节点左边和右边的子树
 
         //递归,再对其进行上述所有步骤,即再区分子树的左、右子子数,直到叶节点
        
        head->left = reConstructBinaryTree(left_pre,left_vin);
        head->right = reConstructBinaryTree(right_pre,right_vin);
        
        return head;

    }
};

5.用堆栈实现pop,push

思路:stack1存的是待push的队列,stack2存的是待删除的队列,两者同一时间有一个为空栈。

class Solution
{
public:
    void push(int node) {  //选定A栈为进栈口,选定B栈为出栈口,进栈时,
        //判断B是否为空,不为空就全部压入A栈,然后A栈push(node)

        while(!stack2.empty())
        {
            stack1.push(stack2.top());
            stack2.pop();
        }
        stack1.push(node);
    }

    int pop() {
//出栈时,判断A是否为空,不为空则全部压入B栈,然后B栈pop()
       while(!stack1.empty())
        {
            stack2.push(stack1.top());
            stack1.pop();
        }
        int result=stack2.top();//空栈的top为-1
        stack2.pop();
        return result;
    }

private:
    stack stack1;
    stack stack2;
};

6.查找一旋转数列的最小值(二分)

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

思路:

 

采用二分法解答这个问题,

mid = low + (high - low)/2

需要考虑三种情况:

(1)array[mid] > array[high]:

出现这种情况的array类似[3,4,5,6,0,1,2],此时最小数字一定在mid的右边。

low = mid + 1

(2)array[mid] == array[high]:

出现这种情况的array类似 [1,0,1,1,1] 或者[1,1,1,0,1],此时最小数字不好判断在mid左边

还是右边,这时只好一个一个试 ,

high = high - 1

(3)array[mid] < array[high]:

出现这种情况的array类似[2,2,3,4,5,6,6],此时最小数字一定就是array[mid]或者在mid的左

边。因为右边必然都是递增的。

high = mid

注意这里有个坑:如果待查询的范围最后只剩两个数,那么mid 一定会指向下标靠前的数字

比如 array = [4,6]

array[low] = 4 ;array[mid] = 4 ; array[high] = 6 ;

如果high = mid - 1,就会产生错误, 因此high = mid

但情形(1)中low = mid + 1就不会错误

class Solution {
public:
    int minNumberInRotateArray(vector rotateArray) {

        int low = 0 ; int high = rotateArray.size() - 1;   
        while(low < high){
            int mid = low + (high - low) / 2;        
            if(rotateArray[mid] > rotateArray[high]){
                low = mid + 1;
            }else if(rotateArray[mid] == rotateArray[high]){
                high = high - 1;
            }else{
                high = mid;
            }   
        }
        return rotateArray[low];
    }
};

7.斐波那契额数列

思路:递归会导致Stack Overflow,尝试用复杂度为 n的普通方法

class Solution {
public:
    int Fibonacci(int n) {

        //方法1:用递归,系统会让一个超大的n来让Stack Overflow,所以
        //递归就不考虑了
         
        //使用迭代法,用fn1和fn2保存计算过程中的结果,并复用起来
        int fn1 = 1;
        int fn2 = 1;
         
        //考虑出错情况
        if (n <= 0) {
            return 0;
        }
        //第一和第二个数直接返回
        if (n == 1 || n == 2) {
            return 1;
        }
 
        //当n>=3时,走这里,用迭代法算出结果
        //这里也说明了,要用三个数操作的情况,其实也可以简化为两
        //个数,从而节省内存空间
        while (n-- > 2) {
            fn1 += fn2;
            fn2 = fn1 - fn2;
        }
        return fn1;
    }
};

8.青蛙跳台阶

思路:本质就是求斐波那契额数列,链接:https://www.nowcoder.com/questionTerminal/8c82a5b80378478f9484d87d1c5f12a4?f=discussion
来源:牛客网
 

对于本题,前提只有 一次 1阶或者2阶的跳法。

a.如果两种跳法,1阶或者2阶,那么假定第一次跳的是一阶,那么剩下的是n-1个台阶,跳法是f(n-1);

b.假定第一次跳的是2阶,那么剩下的是n-2个台阶,跳法是f(n-2)

c.由a\b假设可以得出总跳法为: f(n) = f(n-1) + f(n-2) 

d.然后通过实际的情况可以得出:只有一阶的时候 f(1) = 1 ,只有两阶的时候可以有 f(2) = 2

e.可以发现最终得出的是一个斐波那契数列:

        

              | 1, (n=1)

f(n) =     | 2, (n=2)

              | f(n-1)+f(n-2) ,(n>2,n为整数)


 

9.变态青蛙跳

题目:青蛙可跳<=n   的台阶

思路:

链接:https://www.nowcoder.com/questionTerminal/22243d016f6b47f2a6928b4313c85387?f=discussion
来源:牛客网
 

关于本题,前提是n个台阶会有一次n阶的跳法。分析如下:

f(1) = 1

f(2) = f(2-1) + f(2-2)         //f(2-2) 表示2阶一次跳2阶的次数。

f(3) = f(3-1) + f(3-2) + f(3-3) 

...

f(n) = f(n-1) + f(n-2) + f(n-3) + ... + f(n-(n-1)) + f(n-n) 

 

说明: 

1)这里的f(n) 代表的是n个台阶有一次1,2,...n阶的 跳法数。

2)n = 1时,只有1种跳法,f(1) = 1

3) n = 2时,会有两个跳得方式,一次1阶或者2阶,这回归到了问题(1) ,f(2) = f(2-1) + f(2-2) 

4) n = 3时,会有三种跳得方式,1阶、2阶、3阶,

    那么就是第一次跳出1阶后面剩下:f(3-1);第一次跳出2阶,剩下f(3-2);第一次3阶,那么剩下f(3-3)

    因此结论是f(3) = f(3-1)+f(3-2)+f(3-3)

5) n = n时,会有n中跳的方式,1阶、2阶...n阶,得出结论:

    f(n) = f(n-1)+f(n-2)+...+f(n-(n-1)) + f(n-n) => f(0) + f(1) + f(2) + f(3) + ... + f(n-1)

    

6) 由以上已经是一种结论,但是为了简单,我们可以继续简化:

    f(n-1) = f(0) + f(1)+f(2)+f(3) + ... + f((n-1)-1) = f(0) + f(1) + f(2) + f(3) + ... + f(n-2)

    f(n) = f(0) + f(1) + f(2) + f(3) + ... + f(n-2) + f(n-1) = f(n-1) + f(n-1)

    可以得出:

    f(n) = 2*f(n-1)

    

7) 得出最终结论,在n阶台阶,一次有1、2、...n阶的跳的方式时,总得跳法为:

              | 1       ,(n=0 ) 

f(n) =     | 1       ,(n=1 )

              | 2*f(n-1),(n>=2)

 

10. 填充格子

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

思路:链接:https://www.nowcoder.com/questionTerminal/72a5a919508a4251859fb2cfb987a0e6?f=discussion
来源:牛客网
 

依旧是斐波那契数列

2*n的大矩形,和n个2*1的小矩形

其中target*2为大矩阵的大小

有以下几种情形:

1⃣️target <= 0 大矩形为<= 2*0,直接return 1;

2⃣️target = 1大矩形为2*1,只有一种摆放方法,return1;

3⃣️target = 2 大矩形为2*2,有两种摆放方法,return2;

4⃣️target = n 分为两步考虑:

        第一次摆放一块 2*1 的小矩阵,则摆放方法总共为f(target - 1)

             
             


第一次摆放一块1*2的小矩阵,则摆放方法总共为f(target-2)

因为,摆放了一块1*2的小矩阵(用√√表示),对应下方的1*2(用××表示)摆放方法就确定了,所以为f(targte-2)

           
× ×            

 


11.统计二进制中1的个数

题目:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

思路:

右移:因为负数右移时,在最高位补得是1,二本题最终目的是求1的个数,那么会有无数个,不行

左移:二进制是 前面都是0,最后一位为1,也就是只有一个1,每次向左移位一下,使得flag的二进制表示中始终只有一个位为1,每次与n做位与操作,这样就相当于逐个检测n的每一位是否是1了。

代码:

class Solution {
public:
     int  NumberOf1(int n) {

        int count = 0;
        int flag = 1;
        while (flag != 0) {
            if ((n & flag) != 0) {
                count++;
            }
            flag = flag << 1;
        }
        return count;
     }
};

12.实现幂乘(快速幂)

思路:判断底数、指数的正负0等特殊情况,

2.写出指数的二进制表达,例如13表达为二进制1101。

 * 3.举例:10^1101 = 10^0001*10^0100*10^1000。

代码:

/**
 * 1.全面考察指数的正负、底数是否为零等情况。
 * 2.写出指数的二进制表达,例如13表达为二进制1101。
 * 3.举例:10^1101 = 10^0001*10^0100*10^1000。
 * 4.通过&1和>>1来逐位读取1101,为1时将该位代表的乘数累乘到最终结果。
 */
class Solution {
public:
    double Power(double base, int n) {

    double res = 1,curr = base;
    int exponent;
    if(n>0){
        exponent = n;
    }else if(n<0){
        if(base==0)
            return -1; 
        exponent = -n;
    }else{// n==0
        return 1;// 0的0次方
    }
    while(exponent!=0){
        if((exponent&1)==1)
            res*=curr;
        curr*=curr;// 翻倍
        exponent>>=1;// 右移一位,正数右移添0
    }
    return n>=0?res:(1/res);       
    }
};

13.整理数组中奇偶数(数组、堆栈)

题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

思路:1.首先统计奇数的个数然后新建一个等长数组,设置两个指针,奇数指针从0开始,偶数指针从奇数个数的末尾开始 遍历,填数。时间空间都是  o(n)

2.堆栈

public class Solution {
    public void reOrderArray(int [] array) {

        if(array.length==0||array.length==1) return;
        int oddCount=0,oddBegin=0;
        int[] newArray=new int[array.length];
        for(int i=0;i  odd = new Stack<>();
        Stack  even = new Stack<>();
        for(int i = 0; i=0; i--)
        {
            if(!(even.isEmpty()))
                array[i] = even.pop();
            else
                array[i] = odd.pop();
        }
        */
    }
}

14.链表倒数第k个结点

题目:输入一个链表,输出该链表中倒数第k个结点。

思路:p指针先跑,并且记录节点数,当p指针跑了k-1个节点后,pre指针开始跑,当p指针跑到最后时,pre所指指针就是倒数第k个节点。  注意k大于节点数的时候

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {

        ListNode pre=null,p=null;
        //两个指针都指向头结点
        p=head;
        pre=head;
        //记录k值
        int a=k;
        //记录节点的个数
        int count=0;
        //p指针先跑,并且记录节点数,当p指针跑了k-1个节点后,pre指针开始跑,
        //当p指针跑到最后时,pre所指指针就是倒数第k个节点
        while(p!=null){
            p=p.next;
            count++;
            if(k<1){
                pre=pre.next;
            }
            k--;
        }
        //如果节点个数小于所求的倒数第k个节点,则返回空
        if(count

15.反转链表

题目:输入一个链表,反转链表后,输出新链表的表头。

 

思路:头插法,考虑链表为0和1的情况,剑指offer刷题_第2张图片剑指offer刷题_第3张图片

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {


        ListNode* h = NULL;
        for(ListNode* p = pHead; p; ){
            ListNode* tmp = p -> next;
            p -> next = h;
            h = p;
            p = tmp;
        }
        return h;
    }
};

 16.合成两个排序链表

题目:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

思路:递归

剑指offer刷题_第4张图片剑指offer刷题_第5张图片

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {


        if(pHead1==NULL)
            return pHead2;
        if(pHead2==NULL)
            return pHead1;
        ListNode *res=NULL;
        if(pHead1->val < pHead2->val)
        {
            res = pHead1;
            res->next = Merge(pHead1->next,pHead2);
        }
        else
        {
            res = pHead2;
            res->next = Merge(pHead1,pHead2->next);
        }
        return res;
    }
};

 17.树的子结构

题目:输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)


 

思路:参考剑指offer

1、首先设置标志位result = false,因为一旦匹配成功result就设为true,

剩下的代码不会执行,如果匹配不成功,默认返回false

2、递归思想,如果根节点相同则递归调用DoesTree1HaveTree2(),

如果根节点不相同,则判断tree1的左子树和tree2是否相同,

再判断右子树和tree2是否相同

3、注意null的条件,HasSubTree中,如果两棵树都不为空才进行判断,

DoesTree1HasTree2中,如果Tree2为空,则说明第二棵树遍历完了,即匹配成功,

tree1为空有两种情况(1)如果tree1为空&&tree2不为空说明不匹配,

(2)如果tree1为空,tree2为空,说明匹配。

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {

        boolean result = false;
        //当Tree1和Tree2都不为零的时候,才进行比较。否则直接返回false
        if (root2 != null && root1 != null) {
            //如果找到了对应Tree2的根节点的点
            if(root1.val == root2.val){
                //以这个根节点为为起点判断是否包含Tree2
                result = doesTree1HaveTree2(root1,root2);
            }
            //如果找不到,那么就再去root的左儿子当作起点,去判断时候包含Tree2
            if (!result) {
                result = HasSubtree(root1.left,root2);
            }
             
            //如果还找不到,那么就再去root的右儿子当作起点,去判断时候包含Tree2
            if (!result) {
                result = HasSubtree(root1.right,root2);
               }
            }
            //返回结果
        return result;
    }
    


  public static boolean doesTree1HaveTree2(TreeNode node1, TreeNode node2) {
        //如果Tree2已经遍历完了都能对应的上,返回true
        if (node2 == null) {
            return true;
        }
        //如果Tree2还没有遍历完,Tree1却遍历完了。返回false
        if (node1 == null) {
            return false;
        }
        //如果其中有一个点没有对应上,返回false
        if (node1.val != node2.val) {  
                return false;
        }
         
        //如果根节点对应的上,那么就分别去子节点里面匹配
        return doesTree1HaveTree2(node1.left,node2.left) && doesTree1HaveTree2(node1.right,node2.right);
    }
}

18.二叉树镜像翻转(递归)

题目:

剑指offer刷题_第6张图片

思路:剑指offer刷题_第7张图片 

代码:

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    void Mirror(TreeNode *pRoot) {

        if(pRoot==NULL) return;
        swap(pRoot);
        Mirror(pRoot->left);
        Mirror(pRoot->right);
    }



    void swap(TreeNode* root){
        TreeNode* node = NULL;
        node = root->left;
        root->left = root->right;
        root->right = node;
    }
};

19.顺时针打印矩阵

题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

思路:顺着走,即向右->向下->向左->向上,一共要走(长*宽)步。遇到边界就改变方向,当向上碰到顶的时候,四个边界都缩小。思路简单,一个循环即可!

class Solution {
public:
    vector printMatrix(vector > matrix) {

        vector ret;
        if(matrix.empty())return ret;   //边界,矩阵为空
             
        int x,y,cnt=matrix[0].size()*matrix.size();  //cnt是长*宽
        int rEdge=matrix[0].size()-1,dEdge=matrix.size()-1,lEdge=0,uEdge=0;
        //bool first=true;
        for(x=0,y=0;cnt>0;cnt--){
        ret.push_back(matrix[x][y]);
            //;2
            //go  right
            if(x==uEdge){if(ylEdge)y--;
            else if(y==lEdge){x--;}
            continue;}
             
            //up
            if(y==lEdge){if(x>uEdge+1)x--;
            else if( x==uEdge+1){y++;lEdge++;uEdge++;rEdge--;dEdge--;}
            continue;}
            //;3
        }
        return ret;
      
    }
};

20.找出栈中的最小元素(o(1))

题目:定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。

思路:      因此, 用另一个栈来保存这些元素是再合适不过的了. 我们叫它最小元素栈. 

    每次压栈操作时, 如果压栈元素比当前最小元素更小, 就把这个元素压入最小元素栈, 原本的最小元素就成了次小元素. 同理, 弹栈时, 如果弹出的元素和最小元素栈的栈顶元素相等, 就把最小元素的栈顶弹出.最小栈数量大于等于s

class Solution {
public:
    void push(int value) {

        s.push(value);
        if(sMin.empty())
            sMin.push(value);
        else if(value <= sMin.top())  //次压栈操作时, 如果
            //压栈元素比当前最小元素更小, 就把这个元素压入最小元素栈
            sMin.push(value); //最小栈数量大于等于s
    }
    void pop() {
        if(!s.empty())
        {
            if(s.top() == sMin.top())
                sMin.pop();
            s.pop();
        }
    }
    int top() {
        return s.top();
        
    }
    int min() {
        return sMin.top();
    }
private:
    stack s;
    stack sMin;
};

21.栈的压入弹出序列

题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)

思路:用一个辅助栈来模拟情况,

入栈1,2,3,4,5

出栈4,5,3,2,1

首先1入辅助栈,此时栈顶1≠4,继续入栈2

此时栈顶2≠4,继续入栈3

此时栈顶3≠4,继续入栈4

此时栈顶4=4,出栈4,弹出序列向后一位,此时为5,,辅助栈里面是1,2,3

此时栈顶3≠5,继续入栈5

此时栈顶5=5,出栈5,弹出序列向后一位,此时为3,,辅助栈里面是1,2,3

如果辅助栈还不为空,说明弹出序列不是该栈的弹出顺序

class Solution {
public:
    bool IsPopOrder(vector pushV,vector popV) {
                if(pushV.empty() || popV.empty() || pushV.size()!=popV.size())
            return false;


        stack s;
        int j=0;
        for(int i=0;i

22.层次遍历二叉树(队列实现)

题目:从上往下打印出二叉树的每个节点,同层节点从左至右打印。

思路:用队列把节点指针放入队列

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    vector PrintFromTopToBottom(TreeNode* root) {

        vector res;
        if(root==NULL)
            return res;
        queue q;
        q.push(root);
        while(!q.empty()){
            res.push_back(q.front()->val);
            if(q.front()->left!=NULL)
                q.push(q.front()->left);
            if(q.front()->right!=NULL)
                q.push(q.front()->right);
            q.pop();
        }
        return res;
    }
};

23.二叉树的后序遍历

题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

思路:
 

已知条件后序序列最后一个值为root;二叉搜索树左子树值都比root小,右子树值都比root大。

1、确定root;

2、遍历序列(除去root结点),找到第一个大于root的位置,则该位置左边为左子树,右边为右子树;

3、遍历右子树,若发现有小于root的值,则直接返回false;

4、分别判断左子树和右子树是否仍是二叉搜索树(即递归步骤1、2、3)。

class Solution {
public:
    bool VerifySquenceOfBST(vector sequence) {
       


    if(sequence.size() == 0)  return false;
        vector left, right;  //左子树和右子树序列
        int RootVal = sequence.back();  //获取根节点的值
        int RightStart;  //右子树序列的起始位置
        
        //获取二叉搜索树的左子树序列
        for(RightStart = 0; RightStart < sequence.size() - 1; RightStart++)  //注意这里是
        {                                                                                //sequence.size() - 1,即左子树序列不能包括根节点
            if(sequence[RightStart] > RootVal)  break;
            else left.push_back(sequence[RightStart]);
        }
        
        //获取二叉搜索树的右子树序列
        int i = RightStart;
        for(; i < sequence.size() - 1; i++)  //注意这里是sequence.size() - 1,即右子树序列不能包括根
        {                                                   //节点
            if(sequence[i] < RootVal)  return false;  //右子树序列中如果出现小于根节点的值的情况,返
            else right.push_back(sequence[i]);        // 回false
        }
        
        //判断左子树序列是不是一颗二叉搜索树
        bool LeftSub = true;
        if(left.size())  //有左子树,才去验证左子树
        LeftSub = VerifySquenceOfBST(left);
        
        //判断右子树序列是不是一颗二叉搜索树
        bool RightSub = true;
        if(right.size())  //有右子树,才去验证右子树
        RightSub = VerifySquenceOfBST(right);
        
        return LeftSub && RightSub;  //当左子树和右子树序列同为二叉搜索树时,
                                        //该序列才是二叉树的后序遍历结果
    }                                                   
    }
};

24.二叉树中和为某一值的路径集合

题目:输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)

思路:

非递归法:后序遍历

1.进栈时候,把值同时压入路径的向量数组,修正路径的和

2.出栈时候,先判断和是否相等,且该节点是否是叶节点,

判断完成后保持和栈一致,抛出路径,修改路径的和

3.向量数组和栈的操作要保持一致

*/

代码:

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    vector > FindPath(TreeNode* root,int expectNumber) {
       stack s;
        vector v;
        vector > res;
        while (root || !s.empty()){
            while (root){
                s.push(root); v.push_back(root->val); expectNumber -= root->val;
                //能左就左,否则向右
                root = root->left ? root->left : root->right;
            }
            root = s.top();
            if (expectNumber == 0 && root->left == NULL && root->right == NULL)
                res.push_back(v);
            s.pop(); v.pop_back(); expectNumber += root->val;
            //右子数没遍历就遍历,如果遍历就强迫出栈
            if (!s.empty() && s.top()->left == root)
                root = s.top()->right;  //如果无右节点也没关系,下一次循环root = s.top()
                                        //返回上一层,按照左右中的顺序后序遍历,这里默认为
                                        //为搜索二叉树,左子树小于右子树
            else
                root = NULL;//强迫出栈
        }
        return res;
    }
};

 

26.二叉搜索树与双向链表

题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

思路:中序遍历即可。只需要记录一个pre指针即可。

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {


        if(pRootOfTree == nullptr) return nullptr;
        TreeNode* pre = nullptr;
         
        convertHelper(pRootOfTree, pre);
         
        TreeNode* res = pRootOfTree;
        while(res ->left)
            res = res ->left;
        return res;
    }
    

    void convertHelper(TreeNode* cur, TreeNode*& pre)
    {
        if(cur == nullptr) return;
         
        convertHelper(cur ->left, pre);
         
        cur ->left = pre;
        if(pre) pre ->right = cur;
        pre = cur;
         
        convertHelper(cur ->right, pre);
    }

};

27.字符串的排列(回溯法)

题目:输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

思路:

递归法,问题转换为先固定第一个字符,求剩余字符的排列;求剩余字符排列时跟原问题一样。

(1) 遍历出所有可能出现在第一个位置的字符(即:依次将第一个字符同后面所有字符交换);

(2) 固定第一个字符,求后面字符的排列(即:在第1步的遍历过程中,插入递归进行实现)。

需要注意的几点:

(1) 先确定递归结束的条件,例如本题中可设begin == str.size() - 1; 

(2) 形如 aba 或 aa 等特殊测试用例的情况,vector在进行push_back时是不考虑重复情况的,需要自行控制;

(3) 输出的排列可能不是按字典顺序排列的,可能导致无法完全通过测试用例,考虑输出前排序,或者递归之后取消复位操作。

 

链接:https://www.nowcoder.com/questionTerminal/fe6b651b66ae47d7acce78ffdd9a96c7?f=discussion
来源:牛客网

class Solution {
public:
    vector Permutation(string str)
    {
        vector result;
        if(str.empty()) return result;
         
        Permutation(str,result,0);
         
        // 此时得到的result中排列并不是字典顺序,可以单独再排下序
        sort(result.begin(),result.end());
         
        return result;
    }
     
    void Permutation(string str,vector &result,int begin)
    {
        if(begin == str.size()-1) // 递归结束条件:索引已经指向str最后一个元素时
        {
            if(find(result.begin(),result.end(),str) == result.end())
            {
                // 如果result中不存在str,才添加;避免aa和aa重复添加的情况
                result.push_back(str);
            }
        }
        else
        {
            // 第一次循环i与begin相等,相当于第一个位置自身交换,关键在于之后的循环,
            // 之后i != begin,则会交换两个不同位置上的字符,直到begin==str.size()-1,进行输出;
            for(int i=begin;i

 28.统计字符串中的第一个超过一半的数字

题目:
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

思路:哈希map,存储次数

class Solution {
public:
    int MoreThanHalfNum_Solution(vector numbers) {


        int n = numbers.size();
        //map 记录出现次数
        map m;
        int count;
        for (int i = 0; i < n; i++) {
            count = ++m[numbers[i]];
            if (count > n/2) return numbers[i];
        }
        return 0;
    }
};

29.最小的k个数

题目:输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。

思路:1.

*基于堆排序算法,构建最大堆。时间复杂度为O(nlogk)

*如果用快速排序,时间复杂度为O(nlogn);

*如果用冒泡排序,时间复杂度为O(n*k)


/*
*基于堆排序算法,构建最大堆。时间复杂度为O(nlogk)
*如果用快速排序,时间复杂度为O(nlogn);
*如果用冒泡排序,时间复杂度为O(n*k)
*/
import java.util.ArrayList;
public class Solution {
    public ArrayList GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList list=new ArrayList();
        //检查输入的特殊情况
        if(input==null || input.length<=0 || input.length=0; len--){
            adjustMaxHeapSort(input,len,k-1);
        }
        //从第k个元素开始分别与最大堆的最大值做比较,如果比最大值小,则替换并调整堆。
        //最终堆里的就是最小的K个数。
        int tmp;
        for(int i=k; itemp){
                input[pos]=input[child];
            }else{
                break;
            }
        }
        input[pos]=temp;
    }
}

30.连续子数组的最大和

题目:例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)

思路:

使用动态规划

F(i):以array[i]为末尾元素的子数组的和的最大值,子数组的元素的相对位置不变

F(i)=max(F(i-1)+array[i] , array[i])

res:所有子数组的和的最大值

res=max(res,F(i))

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {


        int res = array[0]; //记录当前所有子数组的和的最大值
        int max=array[0];   //包含array[i]的连续数组最大值
        for (int i = 1; i < array.length; i++) {
            max=Math.max(max+array[i], array[i]);
            res=Math.max(max, res);
        }
        return res;
    }
}

32.把正数数组排成最小数

题目:输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

思路:
所以在这里自定义一个比较大小的函数,比较两个字符串s1, s2大小的时候,先将它们拼接起来,比较s1+s2,和s2+s1那个大,如果s1+s2大,那说明s2应该放前面,所以按这个规则,s2就应该排在s1前面。 ,突然大悟。

import java.util.ArrayList;

public class Solution {
    public String PrintMinNumber(int [] numbers) {


        String str = "";
        for (int i=0; i b){
                    int t = numbers[i];
                    numbers[i] = numbers[j];
                    numbers[j] = t;
                }
                 
            }
        }
        for (int i = 0; i < numbers.length; i++) {
            str += String.valueOf(numbers[i]);
        }
        return str;
    }
}

33.找第N个丑数

题目:把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

思路:

首先从丑数的定义我们知道,一个丑数的因子只有2,3,5,那么丑数p = 2 ^ x * 3 ^ y * 5 ^ z,换句话说一个丑数一定由另一个丑数乘以2或者乘以3或者乘以5得到,那么我们从1开始乘以2,3,5,就得到2,3,5三个丑数,在从这三个丑数出发乘以2,3,5就得到4,6,10,6,9,15,10,15,25九个丑数,我们发现这种方法会得到重复的丑数,而且我们题目要求第N个丑数,这样的方法得到的丑数也是无序的。那么我们可以维护三个队列:

(1)丑数数组: 1

乘以2的队列:2

乘以3的队列:3

乘以5的队列:5

选择三个队列头最小的数2加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;

(2)丑数数组:1,2

乘以2的队列:4

乘以3的队列:3,6

乘以5的队列:5,10

选择三个队列头最小的数3加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;

 

实现思路:

我们没有必要维护三个队列,只需要记录三个指针显示到达哪一步;“|”表示指针,arr表示丑数数组;

(1)1

|2

|3

|5

目前指针指向0,0,0,队列头arr[0] * 2 = 2,  arr[0] * 3 = 3,  arr[0] * 5 = 5

(2)1 2

2 |4

|3 6

|5 10

目前指针指向1,0,0,队列头arr[1] * 2 = 4,  arr[0] * 3 = 3, arr[0] * 5 = 5

(3)1 2 3

2| 4 6

3 |6 9

|5 10 15

目前指针指向1,1,0,队列头arr[1] * 2 = 4,  arr[1] * 3 = 6, arr[0] * 5 = 5

class Solution {
public:
    int GetUglyNumber_Solution(int index) {


        // 0-6的丑数分别为0-6
        if(index < 7) return index;
        //p2,p3,p5分别为三个队列的指针,newNum为从队列头选出来的最小数
        int p2 = 0, p3 = 0, p5 = 0, newNum = 1;
        vector arr;
        arr.push_back(newNum);
        while(arr.size() < index) {
            //选出三个队列头最小的数
            newNum = min(arr[p2] * 2, min(arr[p3] * 3, arr[p5] * 5));
            //这三个if有可能进入一个或者多个,进入多个是三个队列头最小的数有多个的情况
            if(arr[p2] * 2 == newNum) p2++;
            if(arr[p3] * 3 == newNum) p3++;
            if(arr[p5] * 5 == newNum) p5++;
            arr.push_back(newNum);
        }
        return newNum;
    }
};

 

34.统计字符中第一个出现一次的字符

思路:hash表,两次遍历 

35.统计逆序数

题目:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

思路:/*归并排序的改进,把数据分成前后两个数组(递归分到每个数组仅有一个数据项),
合并数组,合并时,出现前面的数组值array[i]大于后面数组值array[j]时;则前面
数组array[i]~array[mid]都是大于array[j]的,count += mid+1 - i
参考剑指Offer,但是感觉剑指Offer归并过程少了一步拷贝过程。
还有就是测试用例输出结果比较大,对每次返回的count mod(1000000007)求余
*/



/*归并排序的改进,把数据分成前后两个数组(递归分到每个数组仅有一个数据项),
合并数组,合并时,出现前面的数组值array[i]大于后面数组值array[j]时;则前面
数组array[i]~array[mid]都是大于array[j]的,count += mid+1 - i
参考剑指Offer,但是感觉剑指Offer归并过程少了一步拷贝过程。
还有就是测试用例输出结果比较大,对每次返回的count mod(1000000007)求余
*/
 
public class Solution {
    public int InversePairs(int [] array) {
        if(array==null||array.length==0)
        {
            return 0;
        }
        int[] copy = new int[array.length];
        for(int i=0;i= high)
        {
            return 0;
        }
        int mid = (low+high)/2;
        int leftCount = InversePairsCore(array,copy,low,mid)%1000000007;
        int rightCount = InversePairsCore(array,copy,mid+1,high)%1000000007;
        int count = 0;
        int i=mid; //前一个数组的最后一个
        int j=high; //后一个数组的最后一个
        int locCopy = high; //辅助数组下标,从最后一个开始
        while(i>=low && j>mid)
        {
            if(array[i]>array[j])
            {
                count += j-mid;  //从小到大排的,mid+1--j都是逆序
                copy[locCopy--] = array[i--];
                if(count>=1000000007)//数值过大求余
                {
                    count%=1000000007;
                }
            }
            else
            {
                copy[locCopy--] = array[j--];
            }
        }
        for(;i>=low;i--)
        {
            copy[locCopy--]=array[i];
        }
        for(;j>mid;j--)
        {
            copy[locCopy--]=array[j];
        }
        for(int s=low;s<=high;s++)
        {
            array[s] = copy[s];
        }
        return (leftCount+rightCount+count)%1000000007;
    }
}

36.计算二叉树的深度

题目:输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

思路:1.层次遍历(队列实现)

2.递归实现


//递归
import java.lang.Math;
public class Solution {
    public int TreeDepth(TreeNode pRoot)
    {
        if(pRoot == null){
            return 0;
        }
        int left = TreeDepth(pRoot.left);
        int right = TreeDepth(pRoot.right);
        return Math.max(left, right) + 1;
    }
}


//队列实现
import java.util.LinkedList;
import java.util.Queue;
public class Solution {
    public int TreeDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
      // return Math.max(TreeDepth(root.left), TreeDepth(root.right)) + 1;
                Queue queue = new LinkedList<>();
        queue.offer(root);
        int count = 0, depth = 0, nextCount = 1;
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            count ++;
            if (node.left != null) {
                queue.offer(node.left);
            }
            if (node.right != null) {
                queue.offer(node.right);
            }
            if (count == nextCount) {   //上述方式是先知道下一层树的个数,
                                        //然后count++,相等时候,结束一层的层序遍历。
                nextCount = queue.size();
                count = 0;
                depth++;
            }
        }
        return depth;

    }
}

37.判断平衡二叉树

题目:输入一棵二叉树,判断该二叉树是否是平衡二叉树。

思路:平衡二叉树(Balanced Binary Tree),具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

代码:

//调用求树深度的函数,缺点:下面的结点会重复计算



public classSolution {
    public boolean IsBalanced_Solution(TreeNode root) {
        if(root == null) {
            return true;
        }
        return Math.abs(maxDepth(root.left) - maxDepth(root.right)) <= 1 &&
            IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right);
    }
      
    private int maxDepth(TreeNode root) {
        if(root == null) {
            return 0;
        }
        return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
    }
}


//如果发现子树不是平衡二叉树,则直接停止遍历,这样至多只对每个结点访问一次。


public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
        return getDepth(root) != -1;
    }
     
    private int getDepth(TreeNode root) {
        if (root == null) return 0;
        int left = getDepth(root.left);
        if (left == -1) return -1;
        int right = getDepth(root.right);
        if (right == -1) return -1;
        return Math.abs(left - right) > 1 ? -1 : 1 + Math.max(left, right);
    }
}

39.和为s的所有连续正数序列

题目:小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!

思路:叫做双指针技术,就是相当于有一个窗口,窗口的左右两边就是两个指针,我们根据窗口内值之和来确定窗口的位置和宽度

class Solution {
public:
    vector > FindContinuousSequence(int sum) {
         vector > result;
        //两个起点,相当于动态窗口的两边,根据其窗口内的值的和来确定窗口的位置和大小
         int plow = 1,phigh = 2;
        while(phigh > plow){
            int cur = (phigh + plow) * (phigh - plow + 1) / 2;
            if(cur == sum){
                vector list;
                for(int i = plow; i <= phigh; i++)
                    list.push_back(i);
                result.push_back(list);
                plow++;
            }else if(cur < sum){
                phigh++; //如果当前窗口内的值之和小于sum,那么右边窗口右移一下
            }else{
                plow++;
            }
        }
        return result;
    }
};

40.和为s的两个数 

题目:输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

思路:
数列满足递增,设两个头尾两个指针i和j,

若ai + aj == sum,就是答案(相差越远乘积越小)

若ai + aj > sum,aj肯定不是答案之一(前面已得出 i 前面的数已是不可能),j -= 1

若ai + aj < sum,ai肯定不是答案之一(前面已得出 j 后面的数已是不可能),i += 1

O(n)

 

41.字符串循环左移

题目:实现字符串的左移

思路:三次反转



//按照剑指offer书上的方法,两次翻转字符串。
class Solution {
public:
    string LeftRotateString(string str, int n) {
        reverse(str.begin(), str.end());
        reverse(str.begin(), str.begin() + str.size() - n);
        reverse(str.begin() + str.size() - n, str.end());
        return str;
    }
};

42.字符串反转成句子

题目:student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。

思路:用栈的方法。本题比较理想,可进一步考虑多空格,末尾有空格等情况。



class Solution {
public:
    string ReverseSentence(string str) {
        int len = str.size(), i = str.size() - 1;
        while(i >= 0){
            stack tmp;
            while(i >= 0 && str[i] != ' '){
                    tmp.push(str[i]); --i;
              
            }
            while(!tmp.empty()){
                str += tmp.top(); tmp.pop();
            }
            str += str[i];
            --i;
        }
        return str.substr(len, len);
    }
};

44.圆圈问题(约瑟夫环)

题目:小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

思路:我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新      的约瑟夫环



import java.util.LinkedList;
 
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        LinkedList list = new LinkedList();
        for (int i = 0; i < n; i ++) {
            list.add(i);
        }
         
        int bt = 0;
        while (list.size() > 1) {
            bt = (bt + m - 1) % list.size();
            list.remove(bt);
        }
         
        return list.size() == 1 ? list.get(0) : -1;
    }
}

45.求1+2+..+n(只使用 ++,--)

题目:求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

思路:
 

1.需利用逻辑与的短路特性实现递归终止。 2.当n==0时,(n>0)&&((sum+=Sum_Solution(n-1))>0)只执行前面的判断,为false,然后直接返回0

3.当n>0时,执行sum+=Sum_Solution(n-1),实现递归计算Sum_Solution(n)。

public class Solution {
    public int Sum_Solution(int n) {
               int sum = n;
        boolean ans = (n>0)&&((sum+=Sum_Solution(n-1))>0);
        return sum;
    }
}

46.用位运算实现加法

题目:写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。



public class Solution {
    public int Add(int num1,int num2) {
        while (num2!=0) {
            int temp = num1^num2;
            num2 = (num1&num2)<<1;
            num1 = temp;
        }
        return num1;
    }
}

 

你可能感兴趣的:(剑指offer刷题)