【刷题笔记/剑指Offer】Part 3 (21-30)

13 篇文章 0 订阅

21. 栈的压入、弹出序列

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

好吧,这个题目之前见过,但是这次遇到还是想不出来。。总之,思路是构建一个栈模仿题目操作,另外需要两个指针分别指向压入序列和弹出序列。压入序列指针和弹出序列指针初始分别指向两个序列的头部,按顺序循环压入元素,在循环体中若压入序列指针指向的元素和弹出序列指向的元素相同,则栈执行弹出操作。最后如果栈为空,则表明序列二是序列一的弹出序列,否则不是。代码如下:

import java.util.*;

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

22. 从上往下打印二叉树

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

这个题目其实就是变相考察二叉树的广度优先遍历,需要用队列辅助完成操作,代码没什么问题,只不过通过这次也顺便熟悉了 java.util  .Queue/.Deque 的常用操作和区别:

1. Queue

主要操作 boolean add(): 在队列末尾加入元素,若成功返回true,不成功抛出异常

              boolean offer():在队列末尾加入元素,若成功返回true,不成功返回false

              E remove():移除队列头的元素,若成功返回元素,不成功抛出异常

              E pull():移除队列头的元素,若成功返回元素,不成功返回null

2. Deque

这个就比较有意思了,它的学名叫做双端队列,既可以当队列用,也可以当栈用,注意这里用add或offer这类队列操作时是向其尾部插入元素,用remove或pull时是移除其头部元素,当用push这样的栈操作时是向其头部插入元素,用pop时是移除其头部元素。所以总结起来:用Deque的时候如果把它当成栈记住是在队列的头进行操作的,一定注意不要用错操作指令,不然很可能像我今天这样,找了半天还是找不到bug,那就蛋疼了。。。

最后,1和2只是接口,当用的时候要这么初始化:

Deque<Integer> deque = new LinkedList<Integer>();
恩,最后附上题目的代码:

import java.util.*;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

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

    }

}
*/
public class Solution {
    public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        if(root == null)
            return list;
        Deque<TreeNode> queue = new LinkedList<TreeNode>();
        queue.add(root);
        while(!queue.isEmpty()){
            TreeNode tmp = queue.pop();
            if(tmp.left != null)
            	queue.add(tmp.left);
            if(tmp.right != null)
                queue.add(tmp.right);
            list.add(tmp.val);
        }
        return list;
    }
}

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

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
讲真,这题我想了半天想仿照21的解法一样利用栈来模仿后续遍历过程,然后进行对照,但是最后还是没想出来,牛客网上大神的解法让我茅塞顿开:

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

所以,找你找这个思路,代码如下:

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

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

输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
好吧,我承认我看了其他大牛的解答才想通的。。。
收获如下: 要想在一个class里访问变量,需要在class里定义变量。 代码如下:
import java.util.ArrayList;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

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

    }

}
*/
public class Solution {
    ArrayList<ArrayList<Integer>> path = new ArrayList<ArrayList<Integer>>();
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        if(root == null)
            return path;
        int[] tmp = new int[1000];
        int index = 0;
        isPath(root, target, tmp, index);
        return path;
    }
    private void isPath(TreeNode root, int target, int[] tmp, int index) {
        if(root == null)
            return;
        target -= root.val;
        tmp[index] = root.val;
        index++;
        if(target == 0 && root.left == null && root.right == null) {
            ArrayList<Integer> list = new ArrayList<Integer>();
            for(int i = 0; i < index; i++) {
                list.add(tmp[i]);
            }
            path.add(list);
            return;
        }
        if(target > 0) {
            if(root.left != null)
         	   isPath(root.left, target, tmp, index);
        	if(root.right != null)
                isPath(root.right, target, tmp, index);
        }
        return;
    }
}

25. 复杂链表的复制

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点)。
这个题目真是让我纠结。。有三种解法:
1. 用临时数组记录每个节点random指针指向第几个节点,这样每次都要从头索引,算法复杂度很高,空间复杂度也很高。
2. 用hashmap来保存节点和random节点的对应关系,空间复杂度比较大
    hashmap是常用的数据结构,用法一定要熟悉。
3. 不新开辟空间
    a. 先复制自己由A->B->C->D到A->A'->B->B'->C->C'->D->D'
    b. 复制random指针
    c. 拆开两个链
但是自己写了程序总是报错,在IDE里调好但是在牛客网提交总是出问题,贴出来大家看看哪里有毛病求指正:
/*
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 p = pHead;
        while(p != null) {
            RandomListNode tmp = new RandomListNode(p.label);
            tmp.next = p.next;
            p.next = tmp;
            p = tmp.next;
        }
        p = pHead;
        while(p != null) {
            if(p.random != null)
            	p.next.random = p.random.next;
            p = p.next.next;
        }
        p = pHead;
        RandomListNode n = p.next;
        RandomListNode tmp = n;
        while(tmp.next != null) {
            p.next = tmp.next;
            p = p.next;
            tmp.next = p.next;
            tmp = tmp.next;
        }
        return n;
    }
}

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

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

考虑到数组的特点:在数组中出现次数超过一半,在遍历胡组的时候保存两个值:一个是数组中的一个数字,一个是次数。当我们遍历到下一个数字的时候,如果下一个数字和我们之前保存的数字相同,则次数加1;如果下个数字和之前 保存的数字不同的话,则次数减1。如果次数为0,我们需要保存下一个数字,并把次数设置为1。由于我们要找的数字肯定是最后一次把次数设为1时对应的数字。再最后,验证该数字是否符合要求即可:

public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        if(array.length == 0)
            return 0;
        int tmp = array[0];
        int count = 1;
        for(int i = 1; i < array.length; i++) {
            if(count == 0){
                tmp = array[i];
                count++;
            }else{
                if(tmp == array[i])
                    count++;
                else{
                    count--;
                }
            }
        }
        if(!Check(array, tmp))
            tmp = 0;
        return tmp;
    }
    public boolean Check(int[] array, int tmp) {
        int count = 0;
        for(int i = 0; i < array.length; i++) {
            if(tmp == array[i])
                count++;
        }
        boolean isMoreThanHalf = true;
        if(count * 2 <= array.length)
            isMoreThanHalf = false;
        return isMoreThanHalf;
    }
}

27. 最小的K个数

输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
所以,这道题目有的说要用冒泡算法的思想,有的说要用堆排列的思想,当然了,这个只需要排列前k个数就ok,所以算法时间复杂度是O(N*K),anyway,代码如下:
import java.util.ArrayList;

public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> array = new ArrayList<Integer>();
        if(input.length < k)
            return array;
        for(int i = 0; i < k; i++) {
            for(int j = 0; j < input.length - i - 1; j++) {
              	if(input[j] < input[j + 1]) {
                    int tmp = input[j];
                    input[j] = input[j + 1];
                    input[j + 1] = tmp;
                }
            }
            array.add(input[input.length - 1 - i]);
        }
        return array;
    }
}

28. 连续子数组的最大和

HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。你会不会被他忽悠住?
动态规划问题,题意指的不一定是从array头开始,可以从中间开始最大的数,代码如下,比较巧妙:
public class Solution {
    public static int FindGreatestSumOfSubArray(int[] array) {
        if(array == null || 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;
    }
}

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

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

递归方法解题思路:
1.将左子树构造成双链表,并返回链表头节点。
2.定位至左子树双链表最后一个节点。
3.如果左子树链表不为空的话,将当前root追加到左子树链表。
4.将右子树构造成双链表,并返回链表头节点。
5.如果右子树链表不为空的话,将该链表追加到root节点之后。
6.根据左子树链表是否为空确定返回的节点。

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

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

    }

}
*/
public class Solution {
    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);
        if(right != null) {
            pRootOfTree.right = right;
            right.left = pRootOfTree;
        }
        return left != null ? left : pRootOfTree;
    }
}

30. 字符串的排列

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

输入描述:输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

讲真,这题目我知道用的是全排列,但是加上输入描述之后有点方。字符只包括大小写字母,那么也就是说A和a也要按一定顺序输出吗?
anyway,让我们带着问题先来看看普通的全排列。全排列问题是比较经典的问题,用递归的方法比较普遍,设有 N 个元素,那么流程也就是:
1. 交换队列头和后面元素的位置,注意这里队列头也要和自己交换,这样才不会忽略情况
2. 对后面的队列进行 N-1 全排列
3. 。。。。。。
递归进行直到剩下一个元素的时候,一个元素的全排列就是自己。附上全排列code:
package algorithms;

public class Permutation {
	public static void permutation(String array) {
		if (array.length() == 0 || array == null) {
			return;
		}
		permutation(array.toCharArray(), 0, array.length() - 1);
	}
	public static void permutation(char[] array, int start, int end) {
		if(end == start) {
			for(int i = 0; i < array.length; i++) {
				System.out.print(array[i] + " ");
			}
			System.out.println();
		}else {
			for(int i = start; i <= end; i++) {
				swap(array, start, i);
				permutation(array, start + 1, end);
				swap(array, start, i);
			}
		}
	}
	public static void swap(char[] array, int i, int j) {
		if(i != j) {
			char tmp = array[i];
			array[i] = array[j];
			array[j] = tmp;
		}
	}
	public static void main(String[] args) {
		String string = "abc";
		permutation(string);
		
	}
}
大家如果还想知道其他解法,请参考:http://www.jb51.net/article/69939.htm



下面看到这个题目,要考虑到剔除重复元素,那么参考大神们的解法可以用已有的 set 来进行存储队列,这样有重复的排序可以自动删除。下面贴上大神代码供大家膜拜:
import java.util.*;
public class Solution {
    public ArrayList<String> Permutation(String str) {
        ArrayList<String> re = new ArrayList<String>();
        if (str == null || str.length() == 0) {
            return re;
        }
        HashSet<String> set = new HashSet<String>();
        fun(set, str.toCharArray(), 0);
        re.addAll(set);
        Collections.sort(re);
        return re;
    }
    void fun(HashSet<String> re, char[] str, int k) {
        if (k == str.length) {
            re.add(new String(str));
            return;
        }
        for (int i = k; i < str.length; i++) {
            swap(str, i, k);
            fun(re, str, k + 1);
            swap(str, i, k);
        }
    }
    void swap(char[] str, int i, int j) {
        if (i != j) {
            char t = str[i];
            str[i] = str[j];
            str[j] = t;
        }
    }
}




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值