第四章 解决面试题的思路

4.1 面试官谈面试思路
  • 先想清楚思路再编码
  • 解释思路
  • 可以画图分析
 
4.2 画图让抽象问题形象化
面试题19:二叉树的镜像
  • 题目:请完成一个函数,输入一个二叉树,该函数输出它的镜像。
  • 思路使用递归或非递归方式实现交换左右结点。
  • 代码实现 递归
    • class TreeNode {
    • public int val;
    • public TreeNode left;
    • public TreeNode right;
    • public TreeNode(int val) {
    • this.val = val;
    • }
    • public TreeNode() {
    • }
    • }
    • public class TestTree {
    • public static int n;
    • public static void main(String[] args) {
    • int[] array1 = new int[] { 11, 22, 33, 0, 0, 0, 44, 0, 55, 0, 0 };
    • TreeNode root1 = CreateTreeBinary(new TreeNode(0), array1);
    • PrintTreeBinary(root1);
    • Mirror(root1);
    • PrintTreeBinary(root1);
    • }
    • public static void PrintTreeBinary(TreeNode treeNode) {
    • if (treeNode != null) {
    • System.err.println(treeNode.val);
    • PrintTreeBinary(treeNode.left);
    • PrintTreeBinary(treeNode.right);
    • }
    • }
    • public static TreeNode CreateTreeBinary(TreeNode treeNode, int[] array) {
    • if (array[n] == 0) {
    • n++;
    • return null;
    • } else {
    • treeNode.val = array[n++];
    • treeNode.left = CreateTreeBinary(new TreeNode(0), array);
    • treeNode.right = CreateTreeBinary(new TreeNode(0), array);
    • return treeNode;
    • }
    • }
    • public static void Mirror(TreeNode root) {
    • if (root == null) {
    • return;
    • }
    • Mirror(root.left);
    • Mirror(root.right);
    • TreeNode tem = root.left;
    • root.left = root.right;
    • root.right = tem;
    • }
    • }
  • 测试用例
    • 功能测试(普通的二义树,二叉树的所有结点都没有左子树或者右子树,只有一一个结点的二叉树)。
    • 特殊输入测试(二叉树的根结点为NULL指针)。
  • 本题考点
    • 考查对二叉树的理解。本题实质上是利用树的遍历算法解决问题。
    • 考查应聘者的思维能力。树的镜像是一个抽象的概念,应聘者需要在短时间内想清楚求镜像的步骤并转化为代码。应聘者可以画图把抽象的问题形象化,这有助于其快速找到解题思路。
  • 本题扩展
    •  上面的代码是用递归实现的。如果要求用循环,该如何实现?
  • 代码实现
    • class TreeNode {
    • public int val;
    • public TreeNode left;
    • public TreeNode right;
    • public TreeNode(int val) {
    • this.val = val;
    • }
    • public TreeNode() {
    • }
    • }
    • public class TestTree {
    • public static int n;
    • public static void main(String[] args) {
    • int[] array1 = new int[] { 11, 22, 33, 0, 0, 0, 44, 0, 55, 0, 0 };
    • TreeNode root1 = CreateTreeBinary(new TreeNode(0), array1);
    • PrintTreeBinary(root1);
    • Mirror(root1);
    • PrintTreeBinary(root1);
    • }
    • public static void PrintTreeBinary(TreeNode treeNode) {
    • if (treeNode != null) {
    • System.err.println(treeNode.val);
    • PrintTreeBinary(treeNode.left);
    • PrintTreeBinary(treeNode.right);
    • }
    • }
    • public static TreeNode CreateTreeBinary(TreeNode treeNode, int[] array) {
    • if (array[n] == 0) {
    • n++;
    • return null;
    • } else {
    • treeNode.val = array[n++];
    • treeNode.left = CreateTreeBinary(new TreeNode(0), array);
    • treeNode.right = CreateTreeBinary(new TreeNode(0), array);
    • return treeNode;
    • }
    • }
    • public static void Mirror(TreeNode root) {
    • if (root == null) {
    • return;
    • }
    • Stack<TreeNode> stack = new Stack<TreeNode>();
    • while (root != null || !stack.isEmpty()) {
    • while (root != null) {
    • TreeNode temp = root.left;
    • root.left = root.right;
    • root.right = temp;
    • stack.push(root);
    • root = root.left;
    • }
    • if (!stack.isEmpty()) {
    • root = stack.pop();
    • root = root.right;
    • }
    • }
    • }
    • }
 
面试题20:顺时针打印矩阵
  • 题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每个数字。
  • 思路:终止行号大于起始行号,终止列号大于起始列号。
  • 代码实现
    • public class testMain {
    • public static void main(String[] args) {
    • int[][] matrix = new int[][]{{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};
    • ArrayList<Integer> list = printMatrix(matrix);
    • System.out.println(list);
    • }
    • public static ArrayList<Integer> printMatrix(int[][] matrix) {
    • ArrayList<Integer> list = new ArrayList<>();
    • if (matrix == null)
    • return list;
    • int start = 0;
    • while (matrix[0].length > start * 2 && matrix.length > start * 2) {
    • printOneCircle(matrix, start, list);
    • start++;
    • }
    • return list;
    • }
    • private static void printOneCircle(int[][] matrix, int start, ArrayList<Integer> list) {
    • int endX = matrix[0].length - 1 - start; // 列
    • int endY = matrix.length - 1 - start; // 行
    • // 从左往右
    • for (int i = start; i <= endX; i++)
    • list.add(matrix[start][i]);
    • // 从上往下
    • if (start < endY) {
    • for (int i = start + 1; i <= endY; i++)
    • list.add(matrix[i][endX]);
    • }
    • // 从右往左(判断是否会重复打印)
    • if (start < endX && start < endY) {
    • for (int i = endX - 1; i >= start; i--)
    • list.add(matrix[endY][i]);
    • }
    • // 从下往上(判断是否会重复打印)
    • if (start < endX && start < endY - 1) {
    • for (int i = endY - 1; i >= start + 1; i--)
    • list.add(matrix[i][start]);
    • }
    • }
    • }
  • 测试用例:
    • 数组有多行多列、数组只有一-行、数组中只有一-列、数组中只有一行一列。
  • 本题考点:
    • 本题主要考查应聘者的思维能力。从外到内顺时针打印矩阵这个过程非常复杂,应聘者如何能很快地找出其规律并写出完整的代码,是解决这道题的关键。当问题比较抽象不容易理解时,可以试着画几个图形帮助理解,这样往往能更快地找到思路。
 
4.3 举例让抽象问题具体化
面试题21:包含min函数的栈
  • 题目:定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的min函数。在该栈中,调用min、push及pop的时间复杂度都是0(1)。
  • 思路:定义两个栈,一个存放入的值。另一个存最小值。
  • 代码实现
    • public class TestMain {
    • private Stack<Integer> stack1 = new Stack<Integer>();
    • private Stack<Integer> stack2 = new Stack<Integer>();
    • public static void main(String[] args) {
    • TestMain t = new TestMain();
    • t.push(4);
    • System.out.println(t.min());
    • t.push(3);
    • System.out.println(t.min());
    • t.push(5);
    • System.out.println(t.min());
    • t.push(1);
    • System.out.println(t.min());
    • t.pop();
    • System.out.println(t.min());
    • }
    • public void push(int node) {
    • stack1.push(node);
    • if (stack2.isEmpty()) {
    • stack2.push(node);
    • } else {
    • if (stack2.peek() > node) {
    • stack2.push(node);
    • }
    • }
    • }
    • public void pop() {
    • if (stack1.pop() == stack2.peek()) {
    • stack2.pop();
    • }
    • }
    • public int top() {
    • return stack1.peek();
    • }
    • public int min() {
    • return stack2.peek();
    • }
    • }
  • 测试用例:
    • 新压入栈的数字比之前的最小值大。
    • 新压入栈的数字比之前的最小值小。
    • 弹出栈的数字不是最小的元素。
    • 弹出栈的数字是最小的元素。
  • 本题考点:
    • 考查分析复杂问题的思维能力。在面试的时候,很多应聘者都止步于添加一个变量保存最小元素的思路。其实只要举个例子多做几次入栈、出栈的操作就能看出问题,并想到也要把最小元素用另外的辅助栈保存。当我们面对一个抽象复杂的问题的时候,可以用几个具体的例子来找出规律。找到规律之后再解决问题,就容易多了。
    • 考查应聘者对栈的理解。
 
面试题22:栈的压入、弹出序列
  • 题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1、2、3、4、5是某栈的压栈序列,序列4、5、3、2、1是该压栈序列对应的一个弹出序列,但4、3、5、1、2就不可能是该压栈序列的弹出序列。
  • 思路:用栈来压入弹出元素,相等则出栈。
  • 代码实现
    • public class TestMain {
    • private Stack<Integer> stack1 = new Stack<Integer>();
    • private Stack<Integer> stack2 = new Stack<Integer>();
    • public static void main(String[] args) {
    • TestMain t = new TestMain();
    • System.out.println(t.IsPopOrder(new int[]{1,2,3,4,5}, new int[]{4,3,5,1,2}));
    • }
    • public boolean IsPopOrder(int[] pushA, int[] popA) {
    • if (pushA == null || popA == null) {
    • return false;
    • }
    • Stack<Integer> stack = new Stack<>();
    • 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 stack.isEmpty();
    • }
    • }
  • 测试用例:
    • 功能测试(输入的两个数组含有多个数字或者只有1个数字,第二个数组是或者不是第-一个数组表示的压入序列对应的栈的弹出序列)。
    • 特殊输入测试(输入两个NULL指针)。
  • 本题考点:
    • 考查分析复杂问题的能力。刚听到这个面试题的时候,很多人可能都没有思路。这个时候,可以通过举- -两个例子,一步步分析压栈、弹出的过程,从中找出规律。
    • 考查应聘者对栈的理解。
 
面试题23:从上往下打印二叉树(层序)
  • 题目:从上往下打印出二叉树的每个结点,同一层的结点按照从左到右的顺序打印。例如输入图4.5中的二叉树,则依次打印出8、6、10、5、7、9、11。
  • 思路:利用队列(链表)辅助实现。
  • 代码实现
    • class TreeNode {
    • public int val;
    • public TreeNode left;
    • public TreeNode right;
    • public TreeNode(int val) {
    • this.val = val;
    • }
    • public TreeNode() {
    • }
    • }
    • public class TestTree {
    • public static int n;
    • public static void main(String[] args) {
    • int[] array = new int[] { 11, 22, 33, 0, 0, 0, 44, 0, 55, 0, 0 };
    • TestTree t = new TestTree();
    • TreeNode root = t.CreateTreeBinary(new TreeNode(), array);
    • ArrayList<Integer> arrayList = t.PrintFromTopToBottom(root);
    • System.out.println(arrayList);
    • }
    • public TreeNode CreateTreeBinary(TreeNode treeNode, int[] array) {
    • if (array[n] == 0) {
    • n++;
    • return null;
    • } else {
    • treeNode.val = array[n++];
    • treeNode.left = CreateTreeBinary(new TreeNode(0), array);
    • treeNode.right = CreateTreeBinary(new TreeNode(0), array);
    • return treeNode;
    • }
    • }
    • public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
    • ArrayList<Integer> list = new ArrayList<>();
    • if (root == null) {
    • return list;
    • }
    • LinkedList<TreeNode> queue = new LinkedList<>();
    • queue.add(root);
    • while (!queue.isEmpty()) {
    • TreeNode node = queue.poll();
    • list.add(node.val);
    • if (node.left != null) {
    • queue.addLast(node.left);
    • }
    • if (node.right != null) {
    • queue.addLast(node.right);
    • }
    • }
    • return list;
    • }
    • }
  • 本题考点:
    • 考查思维能力。按层从上到下遍历二叉树,这对很多应聘者是个新概念,要在短时间内想明白遍历的过程不是一件容易的事情。应聘者通过具体的例子找出其中的规律并想到基于队列的算法,是解决这个问题的关键所在。
    • 考查应聘者对二叉树及队列的理解。
  • 本题扩展:
    • 如何广度优先遍历一个有向图?这同样也可以基于队列实现。树是图的一种特殊退化形式,从上到下按层遍历二叉树,从本质上来说就是广度优先遍历二叉树。
  • 举一反三:
    • 不管是广度优先遍历一个有向图还是一棵树,都要用到队列。第一步我们把起始结点(对树而言是根结点)放入队列中。接下来每一次从队列的头部取出一个结点,遍历这个结点之后把从它能到达的结点(对树而言是子结点)都依次放入队列。我们重复这个遍历过程,直到队列中的结点全部被遍历为止。
 
面试题24:二叉搜索树的后序遍历序列
  • 题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回true ,否则返回false. 假设输入的数组的任意两个数字都互不相同。
  • 思路:先找到右子树的开始位置,然后分别进行左右子树递归处理。
  • 代码实现:
    •  public class TestMain {
    • public static void main(String[] args) {
    • TestMain t = new TestMain();
    • System.out.println(t.VerifySquenceOfBST(new int[] { 5, 7, 6, 9, 11, 10, 8 }));
    • }
    • public boolean VerifySquenceOfBST(int[] sequence) {
    • if (sequence == null || sequence.length == 0)
    • return false;
    • int rstart = 0;
    • int length = sequence.length;
    • for (int i = 0; i < length - 1; i++) {
    • if (sequence[i] < sequence[length - 1])
    • rstart++;
    • }
    • for (int i = rstart; i < length - 1; i++) {
    • if (sequence[i] <= sequence[length - 1]) {
    • return false;
    • }
    • }
    • boolean left = true;
    • if (rstart > 0)
    • left = VerifySquenceOfBST(Arrays.copyOfRange(sequence, 0, rstart));
    • boolean right = true;
    • if (rstart < length - 1)
    • right = VerifySquenceOfBST(Arrays.copyOfRange(sequence, rstart, length - 1));
    • return left && right;
    • }
    • }
  • 测试用例:
    • 功能测试(输入的后序遍历的序列对应一~棵_ 二叉树,包括完全二叉树、所有结点都没有左/右子树的二叉树、只有一一个结点的二叉树;输入的后序遍历的序列没有对应一棵二叉树)。
    • 特殊输入测试(指向后序遍历序列的指针为NULL指针)。
  • 本题考点:
    • 考查分析复杂问题的思维能力。能否解决这道题的关键在于应聘者是否能找出后序遍历的规律。一旦找到规律了,用递归的代码编码相对而言就简单了。在面试的时候,应聘者可以从一两个例子入手,通过分析具体的例子寻找规律。
    • 考查对二叉树后序遍历的理解。
  • 相关题目:
    • 输入一个整数数组,判断该数组是不是某二叉搜索树的前序遍历的结果。这和前面问题的后序遍历很类似,只是在前序遍历得到的序列中,第一个数字是根结点的值。
  • 举一反三:
    • 如果面试题是要求处理一棵二叉树的遍历序列,我们可以先找到二叉树的根结点,再基于根结点把整棵树的遍历序列拆分成左子树对应的子序列和右子树对应的子序列,接下来再递归地处理这两个子序列。本面试题是应用这个思路,面试题6“重建二叉树”也是应用这个思路。
 
面试题25:二叉树中和为某一值的路径
  • 题目:输入一棵二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
  • 思路:先保存根节点,然后分别递归在左右子树中找目标值,若找到即到达叶子节点,打印路径中的值
  • 代码实现:
    • class TreeNode {
    • public int val;
    • public TreeNode left;
    • public TreeNode right;
    • public TreeNode(int val) {
    • this.val = val;
    • }
    • public TreeNode() {
    • }
    • }
    • public class TestTree {
    • public static int n;
    • ArrayList<ArrayList<Integer>> resultList = new ArrayList<ArrayList<Integer>>();
    • ArrayList<Integer> list = new ArrayList<Integer>();
    • public static void main(String[] args) {
    • int[] array = new int[] { 10,5,4,0,0,7,0,0,12,0,0};
    • TestTree t = new TestTree();
    • TreeNode root = t.CreateTreeBinary(new TreeNode(), array);
    • System.out.println(t.FindPath(root, 22));
    • }
    • public TreeNode CreateTreeBinary(TreeNode treeNode, int[] array) {
    • if (array[n] == 0) {
    • n++;
    • return null;
    • } else {
    • treeNode.val = array[n++];
    • treeNode.left = CreateTreeBinary(new TreeNode(0), array);
    • treeNode.right = CreateTreeBinary(new TreeNode(0), array);
    • return treeNode;
    • }
    • }
    • public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
    • if (root == null)
    • return resultList;
    • list.add(root.val);
    • target -= root.val;
    • if (target == 0 && root.left == null && root.right == null) {
    • resultList.add(new ArrayList<>(list));
    • } else {
    • FindPath(root.left, target);
    • FindPath(root.right, target);
    • } // 每返回上一层一次就要回退一个节点
    • list.remove(list.size() - 1);
    • return resultList;
    • }
    • }
  • 测试用例:
    • 功能测试(二叉树中有一条、多条符合条件的路径,二叉树中没有符合条件的路径)。
    • 特殊输入测试(指向二叉树根结点的指针为NULL指针)。
  • 本题考点:
    • 考查分析复杂问题的思维能力。应聘者遇到这个问题的时候,如果一下子没有思路,不妨从一个具体的例子开始,一步步分析路径上包含哪些结点,这样就能找出其中的规律,从而想到解决方案。
    • 考查对二叉树的前序遍历的理解。
 
4.4 分解让复杂问题简单化
面试题26:复杂链表的复制
  • 题目:请实现函数ComplexListNode* Clone(ComplexListNode* pHead),复制一个复杂链表。在复杂链表中,每个结点除了有一个m pNext指针指向下一个结点外,还有一个m pSibling指向链表中的任意结点或者NULL。
  • 思路:
    • 第一步:复制原始链表并创建新节点N',把N'连接到N的后面
    • 第二步:设置N'的random
    • 第三步:把长链表拆分成两个链表,奇数位置节点是原始链表,偶数位置的结点是复制后的链表
  • 代码实现
    • public class Main {
    •     public static class RandomListNode {
    •         int label;
    •         RandomListNode next = null;
    •         RandomListNode random = null;
    •         RandomListNode(int label) {
    •             this.label = label;
    •         }
    •     }
    •    
    •     public RandomListNode Clone(RandomListNode pHead)
    •     {
    •         if(pHead==null) return null;
    •         //复制原始链表并创建新节点N',把N'连接到N的后面
    •         RandomListNode pNode=pHead;
    •         while(pNode!=null){
    •             RandomListNode pClone=new RandomListNode(pNode.label);
    •             pClone.next=pNode.next;
    •             pClone.random=null;
    •             pNode.next=pClone;
    •             pNode=pClone.next;
    •         }
    •        
    •         //设置N'的random
    •         RandomListNode pNode2=pHead;
    •         while(pNode2!=null){
    •             if(pNode2.random!=null){
    •                 pNode2.next.random=pNode2.random.next;
    •             }
    •             pNode2=pNode2.next.next;
    •         }
    •         //把长链表拆分成两个链表,奇数位置节点是原始链表,偶数位置的结点是复制后的链表
    •         RandomListNode pNode3=pHead;
    •         RandomListNode pCloneHead=null;
    •         RandomListNode pCloneNode=null;
    •        
    •         if(pNode3!=null){
    •             pCloneHead=pCloneNode=pNode3.next;
    •             pNode3.next=pCloneNode.next;
    •             pNode3=pNode3.next;
    •         }
    •         while(pNode3!=null){
    •             pCloneNode.next=pNode3.next;
    •             pCloneNode=pCloneNode.next;
    •             pNode3.next=pCloneNode.next;
    •             pNode3=pNode3.next;
    •         }
    •         return pCloneHead;
    •     }
    •     public static void main(String[] args) {
    •         // TODO Auto-generated method stub
    •         RandomListNode head=new RandomListNode(1);
    •         RandomListNode b=new RandomListNode(2);
    •         RandomListNode c=new RandomListNode(3);
    •         RandomListNode d=new RandomListNode(4);
    •         RandomListNode e=new RandomListNode(5);
    •         head.next=b;
    •         head.random=c;
    •         b.next=c;
    •         b.random=e;
    •         c.next=d;
    •         c.random=null;
    •         d.next=e;
    •         d.random=b;
    •         e.next=null;
    •         e.random=null;
    •         Main m=new Main();
    •         RandomListNode temp=null;
    •         temp=m.Clone(head);
    •         while(temp!=null){
    •             System.out.println(temp.label);
    •             temp=temp.next;
    •         }
    •     }
    • }
  • 测试用例:
    • 功能测试(包括结点中的m_ pSibling 指向结点自身,两个结点的m_ pSibling形成环状结构,链表中只有-一个结点)。
    • 特殊输入测试(指向链表头结点的指针为NULL指针)。
  • 本题考点:
    • 考查应聘者对复杂问题的思维能力。本题中的复杂链表是一种不太常见的数据结构,而且复制这种链表的过程也较为复杂。我们把复杂链表的复制过程分解成三个步骤,同时把每-一个步骤 都用图形化的方式表示出来,这些方法都能帮助我们理清思路。写代码的时候,我们为每一个步骤定义-个子函数,最后在复制函数中先后调用者3个函数。有了这些清晰的思路之后再写代码,就容易多了。
    • 考查应聘者分析时间效率和空间效率的能力。当应聘者提出第一种和第二种思路的时候,面试官会提示此时在效率.上还不是最优解。这个时候应聘者要能自己分析出这两种算法的时间复杂度和空间复杂度各是多少。
 
面试题27:二叉搜索树与双向链表
  • 题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。比如输入图4.12中左边的二叉搜索树,则输出转换之后的排序双向链表。
  • 思路:定义一个链表的尾节点,递归处理左右子树,最后返回链表的头节点
  • 代码实现
    • public TreeNode convert(TreeNode pRootOfTree) {
    • TreeNode lastlist = covertNode(pRootOfTree, null);
    • TreeNode pHead = lastlist;
    • while (pHead != null && pHead.left != null) {
    • pHead = pHead.left;
    • }
    • return pHead;
    • }
    • public TreeNode covertNode(TreeNode root, TreeNode lastlist) {
    • if (root == null)
    • return null;
    • TreeNode cur = root;
    • if (cur.left != null) {
    • lastlist = covertNode(cur.left, lastlist);
    • }
    • cur.left = lastlist;
    • if (lastlist != null) {
    • lastlist.right = cur;
    • }
    • lastlist = cur;
    • if (cur.right != null) {
    • lastlist = covertNode(cur.right, lastlist);
    • }
    • return lastlist;
    • }
  • 测试用例:
    • 功能测试(输入的二叉树是完全二叉树,所有结点都没有左/右子树的二叉树,只有一个结点的二叉树)。
    • 特殊输入测试(指向二叉树根结点的指针为NULL指针)。
  • 本题考点:
    • 考查应聘者分析复杂问题的能力。无论是二叉树还是双向链表,都有很多指针。要实现这两种不同数据结构的转换,需要调整大量的指针,因此这个过程会很复杂。为了把这个复杂的问题分析清楚,我们可以把树分为三个部分:根结点、左子树和右子树,然后把左子树中最大的结点、根结点、右子树中最小的结点链接起来。至于如何把左子树和右子树内部的结点链接成链表,那和原来的问题的实质是一-样的,因此可以递归解决。解决这个问题的关键在于把一个大的问题分解成几个小问题,并递归地解决小问题。
    • 考查对二叉树、双向链表的理解及编程能力。
 
面试题28:字符串的排列
  • 题目:输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a、b、c所能排列出来的所有字符串abc、acb、bac、bca、cab 和cba.
  • 思路:将当前位置的字符和前一个字符位置交换,递归。
  • 代码实现
    • public class TestMain {
    • public static void main(String[] args) {
    • TestMain t = new TestMain();
    • System.out.println(t.Permutation("abc"));
    • }
    • public ArrayList<String> Permutation(String str) {
    • ArrayList<String> result = new ArrayList<String>();
    • if (str == null || str.length() == 0)
    • return result;
    • char[] chars = str.toCharArray();
    • TreeSet<String> temp = new TreeSet<>();
    • Permutation(chars, 0, temp);
    • result.addAll(temp);
    • return result;
    • }
    • public void Permutation(char[] chars, int index, TreeSet<String> result) {
    • if (chars == null || chars.length == 0)
    • return;
    • if (index < 0 || index > chars.length - 1)
    • return;
    • if (index == chars.length - 1) {
    • result.add(String.valueOf(chars));
    • } else {
    • for (int i = index; i <= chars.length - 1; i++) {
    • swap(chars, index, i);
    • Permutation(chars, index + 1, result);
    • // 回退
    • swap(chars, index, i);
    • }
    • }
    • }
    • public void swap(char[] c, int a, int b) {
    • char temp = c[a];
    • c[a] = c[b];
    • c[b] = temp;
    • }
    • }
  • 测试用例:
    • 功能测试(输入的字符串中有1 个或者多个字符)。
    • 特殊输入测试(输入的字符串的内容为空或者是NULL指针)。
  • 本题考点:
    • 考查思维能力。当整个问题看起来不能直接解决的时候,应聘者能否想到把字符串分成两部分,从而把大问题分解成小问题来解决,是能否顺利解决这个问题的关键。
    • 考查对递归的理解和编程能力。
  • 本题扩展:
    • 如果不是求字符的所有排列,而是求字符的所有组合,应该怎么办呢?还是输入三个字符a、b、c,则它们的组合有a、b、C、ab、ac、bc、abc.当交换字符串中的两个字符时,虽然能得到两个不同的排列,但却是同一个组合。比如ab和ba是不同的排列,但只算一个组合。
    • 如果输入n个字符,则这n个字符能构成长度为1的组合、长度为2的组合、..... 长度为n的组合。在求n个字符的长度为m (1≤m≤n)的组合的时候,我们把这n个字符分成两部分:第-一个字符和其余的所有字符。如果组合里包含第一个字符,则下- -步在剩余的字符里选取m-1个字符;如果组合里不包含第-一个字符,则下一步在剩余的n-1 个字符里选取m个字符。也就是说,我们可以把求n个字符组成长度为m的组合的问题分解成两个子问题,分别求n-1个字符串中长度为m-1的组合,以及求n-1个字符的长度为m的组合。这两个子问题都可以用递归的方式解决。
  • 举一反三:
    • 如果面试题是按照一定要求摆放若千个数字,我们可以先求出这些数字的所有排列,然后再一一判断每个排列是不是满足题目给定的要求。
 
 
4.5 本章小结
  • 面试的时候我们难免会遇到难题,画图、举例子和分解这三种办法能够帮助我们解决复杂的问题(如图4.17所示)。
                                

 

  • 图形能使抽象的问题形象化。当面试题涉及链表、二叉树等数据结构时,如果在纸上画几张草图,题目中隐藏的规律就有可能变得很直观。
  • 一两个例子能使抽象的问题具体化。很多与算法相关的问题都很抽象,未必一眼就能看出它们的规律。这个时候我们不妨举几个例子,一步一步模拟运行的过程,说不定就能发现其中的规律,从而找到解决问题的窍门。
  • 把复杂问题分解成若千个小问题,是解决很多复杂问题的有效方法。如果我们遇到的问题很大,可以尝试先把大问题分解成小问题,然后再递归地解决这些小问题。分治法、动态规划等方法都是应用分解复杂问题的思路。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

转载于:https://www.cnblogs.com/Sungc/p/9330176.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值