剑指offer

数值的整数次方

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

保证base和exponent不同时为0

需要判断exponent<0的情况。

 public double Power(double base, int exponent) {
        if(exponent==0)return 1;
        if(exponent==1)return base;
        double res=exponent<0?1/Power(base,-(exponent>>1)):Power(base,exponent>>1);
        res*=res;
        if((exponent & 1)==1)res*=base;
        return res;
    }

调整数组顺序使奇数位于偶数前面

利用冒泡排序的思想,前偶后奇就交换

public void reOrderArray(int[] array) {
        for (int i = 0; i < array.length ; i++) {
            for(int j=1;j<array.length-i;j++){
                if ((array[j-1] & 1) == 0 &&((array[j] & 1) == 1)) {
                    int tmp = array[j-1];
                    array[j-1] = array[j];
                    array[j] = tmp;
                }
            }
        }
    }

树的子结构

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

如果根结点相同就调用SameTree(TreeNode l1,TreeNode l2),不同的话进行判断

public boolean SameTree(TreeNode l1,TreeNode l2){
        if(l2==null)return true;
        if(l1==null)return false;
        if(l1.val!=l2.val)return false;
        else return SameTree(l1.left,l2.left)&&SameTree(l1.right,l2.right);
    }
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        if(root1==null||root2==null)return false;
        return SameTree(root1,root2)||HasSubtree(root1.left,root2)||HasSubtree(root1.right,root2);
    }

栈的压入弹出序列

public class Solution {
    private Stack<Integer> stack=new Stack<>();
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        int index=0;
        for(int i=0;i<pushA.length;i++){
            stack.push(pushA[i]);
            while(!stack.isEmpty()&&stack.peek()==popA[index]){
                stack.pop();
                index++;
            }
        }
        return index==popA.length;
    }
}

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

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

分析:后序遍历,左右根的顺序,左子树比根小,右子树比根大,找到第一个比根大的数字作为左右子树的分界,如果右子树中有比根小的数就是false

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

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

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

private ArrayList<ArrayList<Integer>> ans=new ArrayList<>();
    private ArrayList<Integer> tmp=new ArrayList<>();
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        if(root==null)return ans;
        tmp.add(root.val);
        target-=root.val;
        if(0==target&&root.left==null&&root.right==null){
            ans.add(new ArrayList<>(tmp));
        }
        FindPath(root.left,target);
        FindPath(root.right,target);
        tmp.remove(tmp.size()-1);
        return ans;
    }

复杂链表的复制

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

public RandomListNode Clone(RandomListNode pHead){
        if(pHead==null)return null;
        RandomListNode head=new RandomListNode(pHead.label);
        RandomListNode src=head;
        while(pHead.next!=null){
            src.next=new RandomListNode(pHead.next.label);
            if(pHead.random!=null){
                src.random=new RandomListNode(pHead.random.label);
            }
            pHead=pHead.next;
            src=src.next;
        }
        return head;
    }

二叉搜索树与双向链表

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

public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree==null)return null;
        if(pRootOfTree.left==null&&pRootOfTree.right==null)return pRootOfTree;
        //将左子树构造成双链表,并返回链表头结点
        TreeNode left=Convert(pRootOfTree.left);
        TreeNode p=left;
        //找到左子树中最大的结点
        while(p!=null&&p.right!=null){
            p=p.right;
        }
        //如果当前链表不为空的话,将根结点与当前链表相连。
        if(left!=null){
            p.right=pRootOfTree;
            pRootOfTree.left=p;
        }
        //将右子树构造成双向链表,并返回链表头结点
        TreeNode right=Convert(pRootOfTree.right);
        TreeNode q=right;
        //找到右子树中的最小值
        while(q!=null&&q.left!=null){
            q=q.left;
        }
        //如果当前链表不为空的话,将根结点与当前链表相连;
        if(right!=null){
            q.left=pRootOfTree;
            pRootOfTree.right=q;
        }
        return left==null?pRootOfTree:left;
    }

字符串的排列

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

排列交换思想

把字符串分为两部分:第一部分为第一个字符,第二部分为第一个字符以后的字符串。然后接下来求后面那部分的全排列。再将第一个字符与后面的那部分字符逐个交换。

如果遇到重复的字符不进行处理。对链表中的数据进行排序

public void swap(char[] str,int x,int y){
        char t=str[x];
        str[x]=str[y];
        str[y]=t;
    }
    public void dfs(ArrayList<String> ans,int index,char[] str){
        if(index==str.length-1){
            ans.add(String.valueOf(str));
        }
        for(int i=index;i<str.length;i++){
            if(str[i]==str[index]&&i!=index)continue;
            swap(str,index,i);
            dfs(ans,index+1,str);
            swap(str,index,i);
        }
    }
    public ArrayList<String> Permutation(String str) {
        ArrayList<String> ans=new ArrayList<>();
        if(str.length()==0)return ans;
        dfs(ans,0,str.toCharArray());
        Collections.sort(ans);
        return ans;
    }

连续子数组的最大和

模板题

public int FindGreatestSumOfSubArray(int[] array) {
        int ans=array[0];
        int sum=array[0];
        for(int i=1;i<array.length;i++){
            sum=Math.max(sum+array[i],array[i]);
            ans=Math.max(ans,sum);
        }
        return ans;
    }

整数1出现的次数

一般方法

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

O(logn)方法

在分析之前,首先需要知道一个规律:

  • 从 1 至 10,在它们的个位数中,数字1出现了 1 次。
  • 从 1 至 100,在它们的十位数中,数字1出现了 10 次。
  • 从 1 至 1000,在它们的百位数中,数字1出现了 100 次。

依此类推,从 1 至 10^i,在它们右数第二位中,数字1出现了10 ^ (i - 1)次。

对于 n = 2134,要找到从1 ~ 2134这2134个数字中所有1的个数。我们可以对2134进行逐位分析:

(1)在个位上,从1~2130,包含213个10,因此数字1出现了213次,剩下的数字2131、2132、2133、2134中个位数上只有2131包含树脂字1,剩下的都不包含。所以个位数上的数字1的总数为213 + 1 = 214。

(2)在十位上,从1 ~ 2100,包含了21个100,因此数字1出现了21 * 10 = 210次,剩下的数字从2101 ~ 2134,只有2110 ~ 2119这10个数字中十位的数字为1,所以十位上的数字1的总数为210 + 10 = 220。

(3)在百位上,从1 ~ 2000,包含了2个1000,因此数字1出现了2 * 100 = 200次,剩下的数字从2001 ~ 2134,只有2100 ~ 2134这35个数字中的百位的数字为1,所以百位数上数字1的总数为200 + 35= 235。

(4)在千位上,包含了0个10000,因此数字1出现了0 * 1000 = 0次,剩下的数字中只有1000 ~ 1999这1000个数字中的千位的数字为1,所以千位上的数字1的总数为1000。

因此从1 ~ 2134这n个数字中,数字出现的总的次数为 214 + 220 + 235 +1000 = 1669。

总结一下以上的步骤,可以得到这么一个规律:

对于数字n,计算它的第i(i从1开始,从右边开始计数)位数上包含的数字1的个数:

假设第i位上的数字为x的话,则

  • 如果x > 1的话,则第i位数上包含的1的数目为:(高位数字 + 1)* 10 ^ (i-1)  (其中高位数字是从i+1位一直到最高位数构成的数字)
  • 如果x < 1的话,则第i位数上包含的1的数目为:(高位数字 )* 10 ^ (i-1)
  • 如果x == 1的话,则第i位数上包含1的数目为:(高位数字) * 10 ^ (i-1) +(低位数字+1)   (其中低位数字时从第i - 1位数一直到第1位数构成的数字)
public int NumberOf1Between1AndN_Solution(int n) {
        if(n<0)return 0;
        int  high=n;
        int i=1;
        int cnt=0;
        while(high!=0){
            high= (int) (n/Math.pow(10,(i)));
            int tmp= (int) (n/Math.pow(10,(i-1)));
            int cur=tmp%10;
            int  low=(int)(n-tmp*Math.pow(10,(i-1)));
            if(cur<1){
                cnt+=high*Math.pow(10,i-1);
            }
            else if(cur>1){
                cnt+=(high+1)*Math.pow(10,i-1);
            }else{
                cnt+=high*Math.pow(10,i-1);
                cnt+=low+1;
            }
            i++;
        }
        return cnt;
    }

 同理,2出现的次数

public static int numberOf2sInRange(int n) {
        if(n<0)return 0;
        int high=n;
        int i=1;
        int cnt=0;
        while (high!=0){
            high=(int)(n/Math.pow(10,i));
            int tmp=(int)(n/Math.pow(10,(i-1)));
            int cur=tmp%10;
            int low=(int)(n-tmp*Math.pow(10,i-1));
            if(cur<2){
                cnt+=high*Math.pow(10,(i-1));
            }else if(cur>2){
                cnt+=(high+1)*Math.pow(10,i-1);
            }else{
                cnt+=high*Math.pow(10,i-1);
                cnt+=low+1;
                System.out.println(cnt);
            }
            i++;
        }
        return cnt;
    }

把数组排成最小的数

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

通过字符串拼接进行排序

public String PrintMinNumber(int [] numbers) {
        String[] str=new String[numbers.length];
        for(int i=0;i<numbers.length;i++){
            str[i]=String.valueOf(numbers[i]);
        }
        Arrays.sort(str,new Comparator<String>(){
            @Override
            public int compare(String o1, String o2) {
                String c1=o1+o2;
                String c2=o2+o1;
                return c1.compareTo(c2);
            }
        });
        String ans="";
        for(int i=0;i<str.length;i++){
            ans+=str[i];
        }
        return ans;
    }

丑数

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

设置三个条件变量t2,t3,t5,开始都为0,一个数组uglynumber[i],下一个就是

uglynumber[i]=Math.min(uglynumber[t2]*2,Math.min(uglynumber[t3]*3,uglynumber[t5]*5));
 public int GetUglyNumber_Solution(int index) {
        if(index<7)return index;
        int[] uglynumber=new int[index];
        int t2=0,t3=0,t5=0;
        uglynumber[0]=1;
        for(int i=1;i<index;i++){
            uglynumber[i]=Math.min(uglynumber[t2]*2,Math.min(uglynumber[t3]*3,uglynumber[t5]*5));
            if(uglynumber[i]==uglynumber[t2]*2)t2++;
            if(uglynumber[i]==uglynumber[t3]*3)t3++;
            if(uglynumber[i]==uglynumber[t5]*5)t5++;
        }
        return uglynumber[index-1];
    }

数组中的逆序对

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

是归并排序的典型应用。归并排序的基本思想是分治,在治的过程中有前后数字的大小对比,此时就是统计逆序对的最佳时机。

private int cnt=0;
    public void MergeArray(int[] array,int start,int end){
        int[] tmp=new int[end-start+1];
        int left=start,mid=(start+end)/2,right=end;
        int j=mid+1;
        int index=0;
        while(left<=mid&&j<=end){
            if(array[left]<=array[j]){
                tmp[index++]=array[left++];
            }else{
                cnt=(cnt+(mid-left+1))%1000000007;
                tmp[index++]=array[j++];
            }
        }
        while(left<=mid){
            tmp[index++]=array[left++];
        }
        while (j<=end){
            tmp[index++]=array[j++];
        }
        for(int i=start;i<=end;i++){
            array[i]=tmp[i-start];
        }
    }
    public void MergeSortArray(int[] array,int start,int end){
        if(start>=end)return;
        int mid=(start+end)/2;
        MergeSortArray(array,start,mid);
        MergeSortArray(array,mid+1,end);
        MergeArray(array,start,end);
    }
    public int InversePairs(int [] array) {
        if(array.length==0)return 0;
        MergeSortArray(array,0,array.length-1);
        return cnt;
    }

两个链表的第一个公共节点

输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)

假定 List1长度: a+n  List2 长度:b+n, 且 a<b,那么 p1 会先到链表尾部, 这时p2 走到 a+n位置,将p1换成List2头部,接着p2 再走b+n-(n+a) =b-a 步到链表尾部,这时p1也走到List2的b-a位置,还差a步就到可能的第一个公共节点。将p2 换成 List1头部,p2走a步也到可能的第一个公共节点。如果恰好p1==p2,那么p1就是第一个公共节点。或者p1和p2一起走n步到达列表尾部,二者没有公共节点,退出循环。同理a>=b.

public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        if(pHead1==null&&pHead2==null)return null;
        ListNode p1=pHead1,p2=pHead2;
        while (p1!=p2){
            p1=p1==null?pHead2:p1.next;
            p2=p2==null?pHead1:p2.next;
        }
        return p1;
    }

数组中只出现一次的元素

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

^可以找到唯一的元素,因为有两个,可以根据异或结果找到最低位为1的位置,将原本的数组分成两组,分别异或,就得到两个

public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        int a=0;
        for(int i=0;i<array.length;i++){
            a^=array[i];
        }
        int flag=1;
        while ((a&flag)==0){
            flag<<=1;
        }
        for(int i=0;i<array.length;i++){
            if((flag&array[i])==0)num1[0]^=array[i];
            else num2[0]^=array[i];
        }
    }

和为s的连续正数序列

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

利用滑动窗口,求和公式,小于的话就右窗口边界右移,大于的话,左窗口边界右移

public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
       ArrayList<ArrayList<Integer>> ans=new ArrayList<>();
        int left=1;
        int right=2;
        while (left<right){
            int cursum=(right-left+1)*(left+right)/2;
            if(cursum==sum){
                ArrayList<Integer> tmp=new ArrayList<Integer>();
                for(int i=left;i<=right;i++){
                    tmp.add(i);
                }
                ans.add(new ArrayList<>(tmp));
                left++;
            }
            else if(cursum<sum){
                right++;
            }else{
                left++;
            }
        }
        return ans;
    }

最长不重复子串

给定一个字符串,找出最长的不具有重复字符的子串的长度。例如,“abcabcbb”不具有重复字符的最长子串是“abc”,长度为3。对于“bbbbb”,最长的不具有重复字符的子串是“b”,长度为1。

思路:利用滑动窗口,记录最左边的位置

public int lengthOfLongestSubstring(String s) {
        if(s.length()==0)return 0;
        int leftBound=0;
        HashMap<Character,Integer> hashMap=new HashMap<>();
        int max=0;
        for(int i=0;i<s.length();i++){
            char c=s.charAt(i);
            leftBound=Math.max(leftBound,hashMap.containsKey(c)?hashMap.get(c)+1:0);
            max=Math.max(max,i-leftBound+1);
            hashMap.put(c,i);
        }
        return max;
    }

和为S的两个数字

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

跟上一题很相似,左右两个指针

public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
        ArrayList<Integer> ans=new ArrayList<>();
        int left=0,right=array.length-1;
        int res=Integer.MAX_VALUE;
        while(left<=right){
            if(array[left]+array[right]==sum){
                ans.add(array[left]);
                ans.add(array[right]);
                break;
            }
            else if(array[left]+array[right]<sum){
                left++;
            }else right--;
        }
        return ans;
    }

孩子们的游戏

经典的约瑟夫环的问题,可以用公式推导,没理解,使用链表模拟

public int LastRemaining_Solution(int n, int m) {
        LinkedList<Integer> linkedList=new LinkedList<>();
        for(int i=0;i<n;i++){
            linkedList.add(i);
        }
        int index=0;
        while (linkedList.size()>1){
            index=(index+m-1)%linkedList.size();
            linkedList.remove(index);
        }
        return linkedList.size()==1?linkedList.get(0):-1;
    }

不用加减乘除做加法

  1. 两个数异或:相当于每一位相加,而不考虑进位;
  2. 两个数相与,并左移一位:相当于求得进位;
  3. 将上述两步的结果相加
public int Add(int num1,int num2) {
        while(num2!=0){
            int sum=num1^num2;
            int carry=(num1&num2)<<1;
            num1=sum;
            num2=carry;
        }
        return num1;
    }

 字符流中第一个不重复的数

请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。

利用hashmap和arraylist

private HashMap<Character,Integer> map=new HashMap<>();
    private ArrayList<Character> list=new ArrayList<>();
    public void Insert(char ch)
    {
        if(map.containsKey(ch)){
            map.put(ch,map.getOrDefault(ch,0)+1);
        }else{
            map.put(ch,1);
        }
        list.add(ch);
    }
    //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
        for(char key:list){
            if(map.get(key)==1){
                return key;
            }
        }
        return '#';
    }

正则表达式匹配

请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配

如果str和pattern同时结束,那么是匹配的,如果str没有到达,pattern到达则是不匹配的

分为第二个pattern是不是等于*的情况

如果第二个pattern是* 0次匹配,匹配一个,匹配多个三种情况

return matchPattern(str,ss,pattern,ps+2)//匹配0个字符
        ||matchPattern(str,ss+1,pattern,ps+2)//匹配一个字符
        ||matchPattern(str,ss+1,pattern,ps);//匹配多个字符

如果第二个pattern不是*的话,str和pattern同时进行下一个节点匹配

public boolean matchPattern(char[] str,int ss,char[] pattern,int ps){
        if(str.length==ss&&pattern.length==ps)return true;
        if(str.length!=ss&&pattern.length==ps)return false;
        if(ps+1<pattern.length&&pattern[ps+1]=='*'){//pattern的第二个字符是*
            if(ss!=str.length&&(str[ss]==pattern[ps]||pattern[ps]=='.')){
                return matchPattern(str,ss,pattern,ps+2)//匹配0个字符
                        ||matchPattern(str,ss+1,pattern,ps+2)//匹配一个字符
                        ||matchPattern(str,ss+1,pattern,ps);//匹配多个字符
            }
            else{
                return matchPattern(str,ss,pattern,ps+2);//匹配0个字符
            }
        }
        if(ss!=str.length&&(str[ss]==pattern[ps]||pattern[ps]=='.')){
            return matchPattern(str,ss+1,pattern,ps+1);
        }
        return false;
    }
    public boolean match(char[] str, char[] pattern)
    {
        return matchPattern(str,0,pattern,0);
    }

链表中的环入口

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

设置两个快慢指针。如果有环就会相遇。相遇之后,链表起点到入口节点的距离等于相遇点到入口的距离,所以将慢指针重新设置为链表起点,快慢指针一起走即可

public ListNode EntryNodeOfLoop(ListNode pHead){
        ListNode slow=pHead,fast=pHead;
        while (fast!=null&&fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
            if(slow==fast)break;
        }
        if(fast==null||fast.next==null)return null;
        slow=pHead;
        while(slow!=fast){
            slow=slow.next;
            fast=fast.next;
        }
        return slow;
    }

删除链表中重复的节点

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

首先设置一个pre,指向前一个节点,如果前后两个节点一样的话,就一直循环,直到不一样。

public ListNode deleteDuplication(ListNode pHead)
    {
        ListNode dummy=new ListNode(0);
        dummy.next=pHead;
        ListNode pre=dummy,cur=pHead,n;
        while(pHead!=null){
            n=pHead.next;
            if(n!=null&&pHead.val==n.val) {
                while (n != null && pHead.val == n.val) {
                    pHead = pHead.next;
                    n = pHead.next;
                }
                pre.next=pHead.next;
                pHead=pHead.next;
            }
            else {
                pre=pHead;
                pHead= pHead.next;
            }
        }
        return dummy.next;
    }

二叉树的下一个节点

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

当前节点是根结点,下一个节点是右子树的最左节点

当前节点不是根结点, 是左节点,下一个节点是next即父节点

                                  是右节点,下一个节点是父节点的父节点

public TreeLinkNode GetNext(TreeLinkNode pNode)
    {
        if(pNode==null)return null;
        if(pNode.right!=null){
            pNode=pNode.right;
            while (pNode.left!=null){
                pNode=pNode.left;
            }
            return pNode;
        }
        while (pNode.next!=null){
            TreeLinkNode n=pNode.next;
            if(n.left==pNode){
                return n;
            }
            pNode=pNode.next;
        }
        return null;
    }

面试题 04.06. 后继者

难度中等12

设计一个算法,找出二叉搜索树中指定节点的“下一个”节点(也即中序后继)。

如果指定节点没有对应的“下一个”节点,则返回null

与上一题不同,这题有两个节点,而且没有指向父节点的指针,采用中序遍历的思想,如果在左子树里,直接返回。如果当前节点的值大于目标节点,则返回当前节点,否则,在右子树中查找。

public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
        if(root==null)return null;
        TreeNode res=inorderSuccessor(root.left,p);
        if(res!=null)return res;
        if(root.val>p.val)return root;
        return inorderSuccessor(root.right,p);
    }

 

按之字形顺序打印二叉树

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

可以利用ArrayList.add(0,val)的方法实现头插

public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> ans=new ArrayList<>();
        if(pRoot==null)return ans;
        LinkedList<TreeNode> que=new LinkedList<>();
        que.add(pRoot);
        boolean flag=true;
        while (!que.isEmpty()){
            int size=que.size();
            ArrayList<Integer> tmp=new ArrayList<>();
            for(int i=0;i<size;i++){
                TreeNode node=que.poll();
                if(flag)tmp.add(node.val);
                else tmp.add(0,node.val);
                if (node.left != null) que.add(node.left);
                if (node.right != null) que.add(node.right);
            }
            flag=!flag;
            ans.add(tmp);
        }
        return ans;
    }

序列化二叉树

请实现两个函数,分别用来序列化和反序列化二叉树

利用先序遍历,使用#当做空节点,用逗号进行分割

String Serialize(TreeNode root) {
        StringBuilder stringBuilder=new StringBuilder();
        if(root==null){
            stringBuilder.append("#,");
            return stringBuilder.toString();
        }
        stringBuilder.append(root.val+",");
        stringBuilder.append(Serialize(root.left));
        stringBuilder.append(Serialize(root.right));
        return stringBuilder.toString();
    }
    public int index=-1;
    TreeNode Deserialize(String str) {
        index++;
        if(index>=str.length()){
            return null;
        }
        String[] strr=str.split(",");
        TreeNode root=null;
        if(!strr[index].equals("#")){
            root=new TreeNode(Integer.valueOf(strr[index]));
            root.left=Deserialize(str);
            root.right=Deserialize(str);
        }
        return root;
    }

利用中序遍历

297. 二叉树的序列化与反序列化

难度困难184

序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。

请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。

        public class Codec {
            // Encodes a tree to a single string.
            public String serialize(TreeNode root) {
                if(root==null)return "[]";
                Queue<TreeNode> que=new LinkedList<TreeNode>(){{add(root);}};
                StringBuilder ans=new StringBuilder("[");
                while (!que.isEmpty()){
                    TreeNode tmp=que.poll();
                    if(tmp==null){
                        ans.append("null,");
                    }else{
                        ans.append(tmp.val+",");
                        ((LinkedList<TreeNode>) que).add(tmp.left);
                        ((LinkedList<TreeNode>) que).add(tmp.right);
                    }
                }
                ans.deleteCharAt(ans.length()-1);
                ans.append("]");
                return ans.toString();
            }
            // Decodes your encoded data to tree.
            public TreeNode deserialize(String data) {
                if(data.equals("[]"))return null;
                String[] ans=data.substring(1,data.length()-1).split(",");
                TreeNode root=new TreeNode(Integer.parseInt(ans[0]));
                Queue<TreeNode> level=new LinkedList<>();
                ((LinkedList<TreeNode>) level).add(root);
                int index=1;
                while (!level.isEmpty()){
                    TreeNode node=level.poll();
                    if(!ans[index].equals("null")){
                        node.left=new TreeNode(Integer.parseInt(ans[index]));
                        ((LinkedList<TreeNode>) level).add(node.left);
                    }
                    index++;
                    if(!ans[index].equals("null")){
                        node.right=new TreeNode(Integer.parseInt(ans[index]));
                        ((LinkedList<TreeNode>) level).add(node.right);
                    }
                    index++;
                }
                return root;
            }
        }

二叉搜索树第k个节点

给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8)    中,按结点数值大小顺序第三小结点的值为4。

利用中序遍历,左子树一直进栈,直到最左边,然后弹出。

TreeNode KthNode(TreeNode pRoot, int k)
    {
        if(pRoot==null||k==0)return null;
        Stack<TreeNode> stack=new Stack<>();
        int index=0;
        TreeNode cur=pRoot;
        while(!stack.isEmpty()||cur!=null){
            while(cur!=null){
                stack.push(cur);
                cur=cur.left;
            }
            cur=stack.pop();
            index++;
            if(index==k)return new TreeNode(cur.val);
            cur=cur.right;
        }
        return null;
    }

矩阵中的路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。

设置一个访问路径,如果访问过设置为true

public boolean path(char[] matrix,int rs,int rows,int cs,int cols,char[] str,int index,boolean[] flag){
        int k=rs*cols+cs;
        if(rs<0||rs>=rows||cs<0||cs>=cols||matrix[k]!=str[index]||flag[k]==true)return false;
        if(index==str.length-1){
            return true;
        }
        flag[k]=true;
        if(path(matrix,rs+1,rows,cs,cols,str,index+1,flag)
        ||path(matrix,rs-1,rows,cs,cols,str,index+1,flag)
        ||path(matrix,rs,rows,cs-1,cols,str,index+1,flag)
        ||path(matrix,rs,rows,cs+1,cols,str,index+1,flag)) {
            return true;
        }
        flag[k]=false;
        return false;
    }
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
    {
        boolean[] flag=new boolean[matrix.length];
        for(int i=0;i<rows;i++){
            for(int j=0;j<cols;j++){
                if(path(matrix,i,rows,j,cols,str,0,flag)){
                    return true;
                }
            }
        }
        return false;
    }

剪绳子

给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],...,k[m]。请问k[0]xk[1]x...xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

n<=3的情况,m>1必须要分段,例如:3必须分成1、2;1、1、1 ,n=3最大分段乘积是2,

对于n>=4的情况,跟n<=3不同,4可以分很多段,比如分成1、3,这里的3可以不需要再分了,因为3分段最大才2,不分就是3。记录最大的。

public int cutRope(int target) {
        if(target==2)return 1;
        if(target==3)return 2;
        int[] dp=new int[target+1];
        dp[1]=1;
        dp[2]=2;
        dp[3]=3;
        int res=0;
        for(int i=4;i<=target;i++){
            for(int j=1;j<=i/2;j++){
                res=Math.max(res,dp[j]*dp[i-j]);
            }
            dp[i]=res;
        }
        return dp[target];
    }

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值