剑指offer思路(21-30)

21.栈的压入和弹出序列

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

思路:因为弹栈可能在压入n个数后发生(可能是压压弹弹,也可能是压弹压弹),看到栈首先还是想辅助栈的思想!!!

public boolean IsPopOrder(int [] pushA,int [] popA) {
        if (pushA.length <= 0 || popA.length <= 0 || pushA.length != popA.length) {
            return false;
        }

        boolean isOrder = false;

        Stack<Integer> assistStack = new Stack<>();

        for (int i = 0,j = 0; i < pushA.length; i++) {
            assistStack.push(pushA[i]);
            while (!assistStack.empty() && assistStack.peek() == popA[j]) {
                j++;
                assistStack.pop();
            }
        }

        if (assistStack.empty()) {
            isOrder = true;
        }

        return isOrder;

    }

 

22. 从上到下打印二叉树

思路:其实就是层序遍历。

但是可以用LinkedList来模拟queue,用LinkedList.remove(0)模拟出队列(其实也可以用arrayList模拟,但是array remove(0)操作耗时高)

代码:

 public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        ArrayList<Integer> list = new ArrayList<>();

        if (root == null) {
            return list;
        }

        LinkedList<TreeNode> queue = new LinkedList<>();

        queue.add(root);
        list.add(root.val);

        while (!queue.isEmpty()) {
            TreeNode pnode = queue.remove(0);
            if (pnode.left != null) {
                queue.add(pnode.left);
                list.add(pnode.left.val);
            }
            if (pnode.right != null) {
                queue.add(pnode.right);
                list.add(pnode.right.val);
            }
        }
        return list;
}

 

23. 二叉搜索树的后序遍历

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

思路:递归!
BST的后序序列的合法序列是,对于一个序列S,最后一个元素是x (也就是根),如果去掉最后一个元素的序列为T,那么T满足:T可以分成两段,前一段(左子树)小于x,后一段(右子树)大于x,且这两段(子树)都是合法的后序序列。完美的递归定义 : ) 。

注意:(1)是二叉搜索树!根节点的左子树全部小于根节点,右子树大于根节点。

(2)遍历之前,left和right都为true,以防有为空的情况

public boolean VerifySquenceOfBST(int [] sequence) {
        if (sequence.length <= 0){
            return false;
        }

        boolean isPost = false;

        int length = sequence.length;

        isPost = judge(sequence,0,length-1);

        return isPost;
    }

    private boolean judge(int[] sequence, int l,int r) {
        int root = sequence[r];
        int i;
        for (i = l; i < r; i++) {
            if (sequence[i] > root) {
                break;
            }
        }
        //此时的i是右子树后序遍历的第一个节点(右子树左下角)
        int j;
        for (j = i; j < r; j++) {
            if (sequence[j] < root) {
                return false;
            }
        }

        boolean left = true;
        if (i > l) {
            left = judge(sequence,l,i-1);
        }
        boolean right = true;
        if (i < r) {
            right = judge(sequence,i,r-1);
        }

        return left && right;
        
    }

 

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

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

思路:用全局的ArrayList。

注意:1.最后list.remove(list.size()-1);来实现回溯;

2. target一直减,每次可以循环;

3. target到0也不能结束,因为有可能值为0的点

4. target==0之后,list.add(new ArrayList<Integer>(path)); 不能是list.add(path);不重新new的话从始至终listAll中所有引用都指向了同一个一个list,如果直接添加list,下面对list的操作还会改变listAll中list的值

5. 深度优先遍历的时候必须先左后右;

6. 按长度排序可以用lamda表达式

ArrayList<Integer> path = new ArrayList<>();
    ArrayList<ArrayList<Integer>> list = new ArrayList<>();

    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        help(root, target);

//        @Override
//        public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) {
//            if (o1.size()<o2.size()){
//                return 1;
//            }else return -1;
//        }
//    });

        Collections.sort(list, (o1,o2)->o2.size()-o1.size());
        return list;
    }

    public ArrayList<ArrayList<Integer>> help(TreeNode root, int target) {
        if (root == null) {
            return list;
        }

        target = target - root.val;   //因为任何一个路径都一定包括根节点
        if (target >= 0) {
            path.add(root.val);


            if (target == 0 && root.left == null && root.right == null) {
                list.add(new ArrayList<Integer>(path));             //不能是list.add(path);不重新new的话从始至终listAll中所有引用都指向了同一个一个list
            }

            FindPath(root.left,target);         //深度遍历,必须先左后右
            FindPath(root.right,target);

            path.remove(path.size()-1);     //移除最后一个元素,深度遍历完一条路径之后要回退。因为如果遍历到叶子节点发现此叶子节点不符合,需要将次叶节点从list里删除,回溯到叶节点的父节点。
                                                    //并不是没找到路径要回退,找到了也要回退的。因为一直使用了同一个list的空间,遍历完一条路径后回退去找别的路径一定要从list后面删掉回退的元素,不然list会越来越长。

        }
        return list;                    //return list; 这一句在函数里是没有实际意义的,因为返回之后,没有任何操作,而且listAll是全局变量,所以在函数运行的过程中,可以被加入符合要求的路径。
    }


//    返回空值即可
//    private void helper(TreeNode root, int target) {
//        if (root == null) return;
//
//        target = target - root.val;
//
//        if (target >= 0) {
//            path.add(root.val);
//            if (target == 0 && root.left == null && root.right == null) {
//                lists.add(new ArrayList<Integer>(path));
//            }
//
//            FindPath(root.left,target);
//            FindPath(root.right,target);
//
//            path.remove(path.size()-1);
//        }
//    }

 

25. ※复制复杂链表

题:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

思路:1、复制每个节点,如:复制节点A得到A1,将A1插入节点A后面

        2、遍历链表,A1->random = A->random->next;

        3、将链表拆分成原链表和复制后的链表。注意拆分这里,一定是两个链表都要合理,并不是只保证克隆后的!!!

public RandomListNode Clone(RandomListNode pHead)
{
    if (pHead == null) return null;

    //1. 复制每个节点,如复制节点A得到A1,将A1插入到A的后面
    RandomListNode cur = pHead;
    while (cur != null) {
        RandomListNode clone = new RandomListNode(cur.label);
        RandomListNode next = cur.next;
        cur.next = clone;
        clone.next = next;
        cur = next;
    }

    //2.重新遍历列表,复制random指针,如A1.random = A.random.next;(因为这时候A.random会指向一个复制前的节点,如B,其后一个B1才是我们需要的)
    cur = pHead;
    while (cur != null) {
        cur.next.random = cur.random == null ? null : cur.random.next;
        cur = cur.next.next;
    }

    //3. 拆分链表,其实就是隔一个相连
    cur = pHead;
    RandomListNode cloneHead = pHead.next;
    while (cur != null) {
        RandomListNode clone = cur.next;
        cur.next = clone.next;
        clone.next = clone.next == null ? null : clone.next.next;
        cur = cur.next;
    }

    return cloneHead;
}

 

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

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

思路:直接改变节点位置,所以只能改变之前遍历过的(比如中序遍历中的左子树一定先遍历)

代码:

方法1)中序遍历

TreeNode lastLeft = null;//记录最左下的节点,即最小的节点,只会被赋值一次
    TreeNode pre = null;//要么是左子树,要么是父亲节点
    public TreeNode Convert(TreeNode pRootOfTree) {
        if (pRootOfTree == null) {
            return null;
        }

        Convert(pRootOfTree.left);
        pRootOfTree.left = pre;
        if (pre != null) {
            pre.right = pRootOfTree;
        }
        pre = pRootOfTree;
        //只会被赋值一次,因为中序遍历一定是先找到最左下的点,之后就不会改变了,一直是lastLeft
        lastLeft = lastLeft == null ? pRootOfTree : lastLeft;

        Convert(pRootOfTree.right);

        return lastLeft;
    }

方法2)(看论坛!!不会)递归构造双向链表

public TreeNode Convert(TreeNode pRootOfTree) {
    if (pRootOfTree == null) return null;

    if (pRootOfTree.left == null && pRootOfTree.right == null) return pRootOfTree;
    
    //1. 左子树构成双链表,返回头结点
    TreeNode left = Convert(pRootOfTree.left);
    TreeNode tmp = left;
    //2. 定位至左子树最后一个节点
    while (tmp != null && tmp.right != null) {
        tmp = tmp.right;
    }
    //3. 如果左子树不为空,则将root追加
    if (left != null) {
        tmp.right = pRootOfTree;
        pRootOfTree.left = tmp;
    }
    //4. 将右子树构成双链表
    TreeNode right = Convert(pRootOfTree.right);
    //5. 将右子树链表追加到root之后
    if (right != null) {
        pRootOfTree.right = right;
        right.left = pRootOfTree;
    }
    
    return left==null ? pRootOfTree : left;
}

 

 

27. ※※字符串的排列

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

思路:

1. 递归,类似全排列,但是要注意abb这种有重复的问题。参考https://www.cnblogs.com/cxjchen/p/3932949.html

去重的全排列就是从第一个数字起,每个数分别与它后面非重复出现的数字交换。

注意:1. 用HashSet存储字符,HashSet不能重复;

2. void swap函数,不能传递的参数是String类型,因为String中的不能修改(?),必须把String 转成char[](str.toCharArray()),如果最后还是需要String类型,再转回来,但中间打印或者保存的只能是char[]类型的或者转换过去的(String.valueOf(chars))

2. 非递归,字典序,看成是字符串,具有前缀后缀(最长公共前缀)。参考https://www.cnblogs.com/pmars/archive/2013/12/04/3458289.html

 

代码:

1. 递归方式

 public ArrayList<String> Permutation(String str){

        ArrayList<String> list = new ArrayList<String>();
        if(str!=null && str.length()>0){
            PermutationHelper(str.toCharArray(),0,list);
            Collections.sort(list);
        }
        return list;
    }
    private void PermutationHelper(char[] chars,int i,ArrayList<String> list){
        if(i == chars.length-1){
            list.add(String.valueOf(chars));
        }else{
            Set<Character> charSet = new HashSet<Character>();
            for(int j=i;j<chars.length;++j){
                if(j==i || !charSet.contains(chars[j])){
                    charSet.add(chars[j]);
                    swap(chars,i,j);
                    PermutationHelper(chars,i+1,list);
                    swap(chars,j,i);
                }
            }
        }
    }

    private void swap(char[] cs,int i,int j){
        char temp = cs[i];
        cs[i] = cs[j];
        cs[j] = temp;
    }

 

2) 非递归,字典序

注意:reverse(chars,start)代表,chars中start之后的(不包括start)全部反转

public ArrayList<String> Permutation(String str) {
        ArrayList<String> list = new ArrayList<String>();
        if(str==null || str.length()==0){
            return list;
        }
        char[] chars = str.toCharArray();
        Arrays.sort(chars);
        list.add(String.valueOf(chars));

        int fromIndex ,  endIndex;

        while(true) {
            fromIndex = chars.length-1;
            //向前查找第一个变小的元素
            while (fromIndex > 0 && chars[fromIndex] <= chars[fromIndex-1])
                fromIndex--;
            //此时fromIndex指向的是9
            endIndex = fromIndex;
            if (fromIndex == 0)
                break;
            //向后查找第一个大于chars[fromIndex-1]的
            while (endIndex + 1 < chars.length && chars[fromIndex-1] < chars[endIndex+1])
                endIndex++;
            swap(chars,fromIndex-1,endIndex);
            //倒序fromIndex之后的全部
            reverse(chars,fromIndex);

            list.add(String.valueOf(chars));

        }
        return list;
    }

    public void reverse(char[] chars,int start) {
        if (chars == null || chars.length <= 0) {
            return;
        }
        int len = chars.length;
        for (int i = 0; i < (len-start)/2; i++) {
            int p = start+i;
            int q = len - 1 - i;
            if (p != q) {
                swap(chars,p,q);
            }
        }
    }

private void swap(char[] cs,int i,int j){
        char temp = cs[i];
        cs[i] = cs[j];
        cs[j] = temp;
    }

 

28. 数组中出现次数超过一半的数字

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

思路:

1) hashMap,记录出现的次数,一边遍历,一边判断,以空间换时间;

2) 阵地攻守(刚开始选定一个soldier,遇到相同元素count++,遇到不同(敌人)count--,当count=0,则选新的soldier镇守,最后的soilder可能是主元素。最后再通过一次遍历,查看是不是大于一半。

 

代码:

1)hashMap:

public int MoreThanHalfNum_Solution(int [] array) {
        int length = array.length;
        HashMap<Integer,Integer> map = new HashMap<>();
        for (int i = 0;i < length ;i ++) {
            if (!map.containsKey(array[i])) {
                map.put(array[i],1);
            }
            else {
                int times = map.get(array[i]) + 1;
                map.remove(array[i]);
                map.put(array[i],times);
            }
            if (map.get(array[i]) > (length/2)) {
                return array[i];
            }
        }

        return 0;
    }

 

2) soldier:

public int MoreThanHalfNum_Solution(int [] array) {
        if (array == null || array.length <= 0) {
            return 0;
        }

        int result = 0;

        int soldier = array[0];
        int count = 1;
        for (int i = 1; i < array.length; i++) {
            if (count == 0) {
                soldier = array[i];
                count = 1;
                continue;
            }
            if (array[i] == soldier) {
                count++;
            }
            else {
                count--;
            }

        }

        count = 0;
        for (int i = 0; i < array.length; i++) {
            if (array[i] == soldier) {
                count++;
            }
            if (count > (array.length) / 2) {
                result = soldier;
                break;
            }
        }

        return result;
    }

 

29. 最小的k个数

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

思路:排序,选前k个

代码:

1)快排:

public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> array = new ArrayList<>();
        if (k <= 0 || k >input.length) {
            /**不能返回null,要返回一个空的集合*/
            return new ArrayList<Integer>();
        }

        quickSort(input,0,input.length-1);

        for (int i = 0; i < k; i++) {
            array.add(input[i]);
        }

        return array;
    }

    public void quickSort(int[] input,int p,int q) {
        int partition;
        if (p < q) {
            partition= partition(input,p,q);
            quickSort(input,p,partition-1);
            quickSort(input,partition+1,q);
        }
    }

    public int partition(int[] input, int p, int q) {
        int temp = input[p];
        int i = p,j = q;
        while (i != j) {
            while (input[j] >= temp && i < j) {  /**必须是i<j,不能是<=*/
                j--;
            }
            while (input[i] <= temp && i < j) {/**必须是i<j,不能是<=*/
                i++;
            }
            if (i < j) {
                swap(input,i,j);
            }
        }

        /***======================必须==================**/
        input[p] = input[i];
        input[i] = temp;
        /***======================必须==================**/
        return i;
    }

    public void swap(int[] input,int l, int r) {
        int temp = input[l];
        input[l] = input[r];
        input[r] = temp;
    }

2)堆排序

private void heapSort(int[] input) {
        //1. 构建初始大顶堆
        for (int i = input.length/2 - 1 ; i >= 0 ;i--) {
//            从第一个非叶子节点开始,从下往上,从左往右
            adjustHeap(input,i,input.length);
        }
//        2. 调整堆结构,堆顶元素与末尾元素交换
        for (int i = input.length-1; i >= 0; i--) {
            swap(input,0,i);
//            重新调整
            adjustHeap(input,0,i);
        }
    }

    /**
     * 调整大顶堆,仅仅是调整,这时候大顶堆已经建立好
     * @param arr
     * @param start
     * @param length
     */
    private void adjustHeap(int[] arr, int start, int length) {
        int temp = arr[start];
        // start的左子树
        for (int i = start*2+1;i < length;i = i*2+1) {
            //有右子树且左子树的值小于右子树的值,就是找出两个子树中比较大的那一个
            if (i+1 < length && arr[i] < arr[i+1]) {
                i++;
            }
            if (arr[i] > temp) {
                arr[start] = arr[i];
                start = i;
            }
            else {
                break;
            }
        }
        arr[start] = temp;
    }

    private void swap(int[] arr,int i,int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

3) 快排思想,只用partition,找到前k个就好

public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> list = new ArrayList<>();
        if (k <= 0 || k > input.length || input.length <= 0) {
            return list;
        }

        int q = partition(input,0,input.length-1);

        int l = 0, r = input.length-1;

        while (q != k-1) {
            if (q > k-1) {
                r = q-1;
                q = partition(input,l,r);
            }
            else {
                l = q+1;
                q = partition(input,l,r);
            }
        }

        for (int i = 0; i < k; i++) {
            list.add(input[i]);
        }

        return list;
    }

 

 

30. 连续子数组的最大和

题:最大子数组和

思路:1)规律性的,如果当前的curSum<0,则curSum+arr[i]一定小于arr[i],所以直接置curSum=arr[i]

2)动态规划!动态规划的重点是:保留中间结果,dp[i] = max{dp[i-1]+array[i],array[i]}.

代码:

1)规律

public int FindGreatestSumOfSubArray(int[] array) {
        if (array.length <= 0 || array == null) {
            return 0;
        }
        int curSum = 0; //当前和
        int maxSum = Integer.MIN_VALUE; //记录最大和

        for (int i = 0; i < array.length; i++) {
            curSum = (curSum < 0) ? array[i] : (curSum + array[i]);

            if (curSum > maxSum) {
                maxSum = curSum;
            }
        }
        return maxSum;
    }

2)动态规划

public int FindGreatestSumOfSubArray(int[] array) {
        if (array.length <= 0 || array == null) {
            return 0;
        }

        int[] dp = new int[array.length];

        for (int i = 0; i < array.length; i++) {
            dp[i] = array[i];
        }

        for (int i = 1; i < array.length; i++) {
            dp[i] = max(dp[i-1]+array[i],array[i]);
        }

        int maxSum = array[0];
        for (int i = 0; i < array.length; i++) {
            if (dp[i] > maxSum) {
                maxSum = dp[i];
            }
        }

        return maxSum;
    }

    int max(int a,int b) {
        return (a>b)? a : b;
    }

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值