剑指Offer编程整理(三)

1、二叉搜索树的后序遍历序列

2、二叉树中和为某一值的路径

3、复杂链表的复制

4、二叉搜索树与双向链表

5、字符串的排列

6、数组中出现次数超过一半的数字

7、最小的k个数

8、连续子数组的最大和

9、整数中1出现的次数(从1到n中1出现的次数)

10、把数组排成最小的数

11、丑数


1、二叉搜索树的后序遍历序列

(1)问题描述:

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

(2)解题思路:

二叉搜索树:即二叉排序树,它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右

子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉搜索树

后序遍历:左右根;

综上可知,数组的最后一个元素为根节点;

数组的前n-1个元素可分为两部分,左边的值小于右边的值;

递归判断没一棵左右子树。

(3)代码实现:

 public boolean VerifySquenceOfBST(int [] sequence) {
        if(sequence==null||sequence.length==0)
            return false;
        boolean res=getRes(sequence,0,sequence.length-1);
        return res;
    }
    public boolean getRes(int[] s,int start,int end){
        int i=0;
        int j=0;
        if(end-start<=1)
            return true;
        for(i=start;i<end;i++){
            if(s[i]>s[end])
                break;
        }
        for(j=i;j<end;j++){
            if(s[j]<s[end])
                return false;
        }
        boolean left=true;
        boolean right=true;
        if(i>0){
            left=getRes(s,start,i-1);
        }
        if(i<s.length-1){
            right=getRes(s,i,end-1);
        }
        return left&&right;
    }

补充:

Arrays.copyOfRange(T[ ] original,int from,int to)将一个原始的数组original,从小标from开始复制,复制到小标to,生成一个新的数组,包括下标from,


包括下标to,效率和clone基本一致,都是native method,比利用循环复制数组效率要高得多。


2、二叉树中和为某一值的路径

(1)问题描述:

输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。

树节点的定义:

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

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

    }

}

(2)解题思路:

深度遍历,一边遍历一边累加。

(3)代码实现:

public class Solution {
    ArrayList<ArrayList<Integer>> result=new ArrayList<ArrayList<Integer>>();
    ArrayList<Integer> arr=new ArrayList<Integer>();
    int num=0;
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        if(root==null)
            return result;
        boolean isLeaf=root.left==null&&root.right==null;
        num+=root.val;
        arr.add(root.val);
        if(num==target&&isLeaf){
            ArrayList<Integer> path=new ArrayList<Integer>();
            for(int i=0;i<arr.size();i++){
                path.add(arr.get(i));
            }
            result.add(path);
        }
        if(num<target&&root.left!=null){
            FindPath(root.left,target);
        }
        if(num<target&&root.right!=null){
            FindPath(root.right,target);
        }
        num-=root.val;
        arr.remove(arr.size()-1);
        return result;
    }
}
3、复杂链表的复制

(1)问题描述:

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注

意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

(2)解题思路:

法一:把复制过程分成两步,第一步复制原始链表上的每一个结点,并用next链接起来;第二步定位每一个结点的random都需要从链表的头结点开始才能找到;

法二:空间换时间,第一步复制原始链表上的每一个结点N并新建N’,然后把这些新建出来的结点用next链接起来。同时把的配对信息放到一个哈希表中。第二步

如果原始链表中结点N的random指针指向S,在复制链表中,对应的N’指向S’,可以利用哈希表根据S找到S’。

法三:第一步复制原始链表上的每一个结点N创建对应的N’,并把N’放在N之后;第二步:设置每个结点的random 指针。如果原始链表上的结点N的random指向S,则对应的复制结点N’的random指向S’; 第三步:把长链表分为两个链表:把奇数位置的结点用next连接起来就是原始链表,把偶数位置的结点用next连接起来就是复制出来的链表。

(3)代码实现:
法三:

/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
public class Solution {
    public RandomListNode Clone(RandomListNode pHead)
    {
        if(pHead==null)
            return null;
        RandomListNode pCur=pHead;
        while(pCur!=null){
            RandomListNode node=new RandomListNode(pCur.label);
            node.next=pCur.next;
            pCur.next=node;
            pCur=node.next;
        }
        pCur=pHead;
        while(pCur!=null){
            if(pCur.random!=null)
                pCur.next.random=pCur.random.next;
            pCur=pCur.next.next;
        }
        RandomListNode head=pHead.next;
        RandomListNode cur=head;
        pCur=pHead;
        while(pCur!=null){
            pCur.next=pCur.next.next;
            if(cur.next!=null)
                cur.next=cur.next.next;
            cur=cur.next;
            pCur=pCur.next;
        }
        return head;
    }
}
4、二叉搜索树与双向链表

(1)问题描述:

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

(2)解题思路:

将左子树构造成双链表,并返回链表头节点。

定位至左子树双链表最后一个节点。

如果左子树链表不为空的话,将当前root追加到左子树链表。

将右子树构造成双链表,并返回链表头节点。

如果右子树链表不为空的话,将该链表追加到root节点之后。根据左子树链表是否为空确定返回的节点。

(3)代码实现:

public TreeNode Convert(TreeNode root) {
        if(root==null)
            return null;
        if(root.left==null&&root.right==null)
            return root;
        // 1.将左子树构造成双链表,并返回链表头节点
        TreeNode left = Convert(root.left);
        TreeNode p = left;
        // 2.定位至左子树双链表最后一个节点
        while(p!=null&&p.right!=null){
            p = p.right;
        }
        // 3.如果左子树链表不为空的话,将当前root追加到左子树链表
        if(left!=null){
            p.right = root;
            root.left = p;
        }
        // 4.将右子树构造成双链表,并返回链表头节点
        TreeNode right = Convert(root.right);
        // 5.如果右子树链表不为空的话,将该链表追加到root节点之后
        if(right!=null){
            right.left = root;
            root.right = right;
        }
        return left!=null?left:root;       
    }
5、字符串的排列

(1)问题描述:

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

(2)解题思路:

列出第一位所有可能情况,后面的位递归;

第一位和每个位交换;
递归的结束条件是第一位和最后一位交换完成。

(3)代码实现:

import java.util.ArrayList;
import java.util.Collections;
public class Solution
{
    public ArrayList<String> Permutation(String str)
    {
        ArrayList<String> res=new ArrayList<String>();
        if(str.length()==0||str==null)return res;
        int n= str.length();
        helper(res,0,str.toCharArray());
        Collections.sort(res);//要求排序,所以全部打印出来后,用sort排一下序
        return res;
         
    }
    public void helper( ArrayList<String> res,int index,char []s)
    {
        if(index==s.length-1)res.add(new String(s));递归结束的条件就是,第一位和最后一位交换完成
        for(int i=index;i<s.length;i++)//让第一位和每一位交换
        {
            if(i==index||s[index]!=s[i])//如果第一位和非第一位重复了,就不交换了
            {
                swap(s,index,i);
                helper(res,index+1,s);递归,第一位固定了,将除了第一位,后面的字符串全排序。
                swap(s,index,i);//交换完成还要换回来,保证第一位不变
            }
        }
         
    }
     
    public void swap(char[]t,int i,int j)
     {
        char c=t[i];
        t[i]=t[j];
        t[j]=c;
    }
}

6、数组中出现次数超过一半的数字

(1)问题描述:

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

(2)解题思路:

如果数组中某数字出现的次数超过数组长度的一半,那么在排序后,该数一定在数组的中间;

先进行快排,然后取数组最中间的数;

遍历该数组,如果最中间的数出现的次数超过数组长度的一半,则输出这个数。

(3)代码实现:

public int MoreThanHalfNum_Solution(int [] array) {
        int len=array.length;
        if(len<1){
            return 0;
        }
        int count=0;
        Arrays.sort(array);
        int num=array[len/2];
        for(int i=0;i<len;i++){
            if(num==array[i])
                count++;
        }
        if(count<=(len/2)){
            num=0;
        }
        return num;
    }

7、最小的K个数

(1)问题描述:

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

(2)解题思路:

法一:将数组进行快排;取快排后前K个数。

法二:将数组前k个数放到一个copy到一个数组int[] temp中;将temp数组快排;取从原数组第k个元素开始,逐个与temp数组中最大的元素temp[k-1]进行比较;如果temp[k-1]

比原数组小,则原数组取下一个,如果temp[k-1],大于原数组中元素,则两个数进行交换,temp数组快排;最后输出temp数组。

(3)代码实现:

法一:略

法二:

public static ArrayList<Integer> getLastKnum(int[] input,int k){
        ArrayList<Integer> list = new ArrayList<Integer>();
        int[] temp = new int[k];
        temp = Arrays.copyOfRange(input,0,k);
        Arrays.sort(temp);
        for (int i = k ;i<input.length;i++){
            if (temp[k-1] > input[i]){
                int a = temp[k-1];
                temp[k-1] = input[i];
                input[i] = a;
                Arrays.sort(temp);
            }
        }
        for (int i : temp){
            list.add(i);
        }
        return list;
    }
8、连续子数组的最大和

(1)问题描述:

HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。你会不会被他忽悠住?(子向量的长度至少是1)

(2)解题思路:

法一:蛮力法,找到所有的子数组;

法二:重复利用已经计算的子数组之和;

法三:动态规划:

令currSum是以当前元素结尾的最大连续子数组的和;

maxSum是全局的最大子数组的和;

当往后扫描时:对第j个元素有两种选择,要么放入前面找到的子数组,要么最为新的子数组的第一个元素;

如果currSum > 0,则令currSum加上a[j];

如果currSum < 0,则currSum被置为当前元素;即currSum = a[j];

(3)代码实现:

法一:略

法二:

public int FindGreatestSumOfSubArray(int[] array) {
        int max=array[0];
        for(int i=0;i<array.length;i++){
            int sum=0;
            for(int j=i;j<array.length;j++){
                sum+=array[j];
                if(sum>max)
                    max=sum;
            }
        }
        return max;
    }
法三:

public static int FindGreatestSumOfSubArray(int[] array) {
        if (array.length == 0 ){
            return 0;
        }
        int currSum = 0;
        int maxSum = array[0];
        for (int j=0 ;j<array.length;j++){
            if (currSum >= 0){
                currSum += array[j];
            }else {
                currSum = array[j];
            }

            if (currSum > maxSum){
                maxSum = currSum;
            }
        }

        return maxSum;
    }

9、整数中1出现的次数(从1到n整数中1出现的次数)

(1)问题描述:

求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数。

(2)解题思路:

法一:字符数组;

法二:数学方法。

(3)代码实现:

法一:

public int NumberOf1Between1AndN_Solution(int n) {
    
        int count=0;
        while(n>0){
            String str=String.valueOf(n);
            char[] chars=str.toCharArray();
            for(int i=0;i<chars.length;i++){
                if(chars[i]=='1')
                    count++;
            }
            n--;
        }
        return count;
    }

法二:

public static int NumberOf1Between1AndN_Solution(int n) {
        int count = 0;
        for (long m = 1; m <= n; m *= 10)
            count += (n/m + 8) / 10 * m + (n/m % 10 == 1 ? n%m + 1 : 0);
        return count;
    }

10、把数组排成最小的数

(1)问题描述:

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

(2)解题思路:

"3"与"32"不能直接进行"3".compareTo("32")的运算;而是将两个字符串拼接后进行再进行比较"332".compareTo("323")

(3)代码实现:

 public String PrintMinNumber(int [] numbers) {

        if(numbers==null||numbers.length==0)
            return "";
        int len=numbers.length;
        String[] str=new String[len];
        StringBuilder sb=new StringBuilder();
        for(int i=0;i<len;i++){
            str[i]=String.valueOf(numbers[i]);
        }
        Arrays.sort(str,new Comparator<String>(){
            public int compare(String s1,String s2){
                String c1=s1+s2;
                String c2=s2+s1;
                return c1.compareTo(c2);
            }
        });
        for(int i=0;i<len;i++){
            sb.append(str[i]);
        }
        return sb.toString();
    }

11、丑数

(1)问题描述:

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

(2)解题思路:

法一:逐个判断每个整数是不是丑数,根据丑数的定义,丑数只能被2,3,5整除。也就是说如果一个数能被2整除,我们把它连续除以2;如果能被3整除,就连续除以3;如果能被5整除,就除以5.如果最后我们得到的是1,那么这个数就是丑数,否则不是。

法二:创建数组保存已经找到的丑数,用空间换时间,根据丑数的定义,丑数应该是另一个丑数乘以2,3,5的结果,创建一个数组,里面的数字是排序好的丑数,每一个丑数都是前面的丑数乘以2,3,5得到的。

(3)代码实现:

法一:略;

法二;

public int GetUglyNumber_Solution(int index) {
        if(index<=0)
            return 0;
        int[] result=new int[index];
        int count=0;
        int i2=0;
        int i3=0;
        int i5=0;
        result[0]=1;
        int tmp=0;
        while(count<index-1){
            tmp=min(result[i2]*2,min(result[i3]*3,result[i5]*5));
            if(tmp==result[i2]*2) i2++;
            if(tmp==result[i3]*3) i3++;
            if(tmp==result[i5]*5) i5++;
            result[++count]=tmp;
        }
        return result[index-1];
    }
    private int min(int a,int b){
        return (a>b)?b:a;
    }


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值