常见算法总结(2)

本博客中的算法均使用java语言写就,其中有我自己写的,也有参考了其它人的写法(多数是看到别人用C/C++写算法的)进行修改的,如若大家觉得其中代码有什么问题的话,欢迎写在评论上,我会及时进行修改和改进。

1,反转一个单向链表。

链表是一种常见的数据结构,面试题目中也经常遇到,反转链表是其中一个示例。

为了反转这个单向链表,我在这里准备了一个数据结构,用以模拟链表中的节点:

 1 /* Node of single Linked List */
 2     private static class SNode {
 3 
 4         public SNode(int value) {
 5             this.value = value;
 6         }
 7 
 8         public int value;
 9         public SNode next;
10     }
View Code

其中有两个域,分别表示当前节点的值和下一个节点。

然后反转链表的代码为:

 1 public static SNode reverseSingleList(SNode head) {
 2         SNode reverseHead = null;
 3         SNode nextNode = head;
 4         SNode prevNode = null;
 5         while (nextNode != null) {
 6             SNode next = nextNode.next;
 7             if (next == null) {
 8                 reverseHead = nextNode;//can't be next !!!
 9             }
10             nextNode.next = prevNode;
11             prevNode = nextNode;
12             nextNode = next;//can't be nextNode.next !!!
13         }
14         return reverseHead;
15     }
View Code

其中while的判断条件为当前节点不为null,当然,只有在传入的参数head为null时,while才不会运行。

然后其中的if语句用于获取传入的链表的最后一个节点,即要返回的链表的头节点。

然后进行一下测试,测试用例如下:

1                 SNode head = new SNode(0);
2         SNode head1 = new SNode(1);
3         SNode head2 = new SNode(2);
4         SNode head3 = new SNode(3);
5         head.next = head1;
6         head1.next = head2;
7         head2.next = head3;
8         printSingleList(head);// 0, 1, 2, 3
9         printSingleList(reverseSingleList(head));//3, 2, 1, 0        
View Code

运行一下就可以看到结果。其中顺序打印链表的方法为:

1 public static void printSingleList(SNode head) {
2         if (head == null) {
3             return;
4         }
5         System.out.println(head.value);
6         printSingleList(head.next);
7     }
View Code

2,将一个二元搜索树(也叫二叉排序树)转化成一个排序的双向链表。

我们知道二元搜索树跟双向链表有一个共同点,即二者的结点教师由三个域组成的,即一个表示值,然后有两个引用,分别指向满足条件的结点,即对于二元搜索树而言,一个指向左子树,一个指向右子树;而对于双向链表而言,则一个指向前一个结点,一个指向后一个结点。因而这个共同点便促成了可以转化的可能性。

二元搜索树的性质是根结点的值大于左子结点的值,而小于右子结点的值。而二元搜索树的中序遍历则满足升序排序的特点。因而可以利用二元搜索树的中序遍历来转化成升序的双向链表。

因为,准备了一个数据结构用以模拟二元搜索树的结点:

 1 /* Node of binary tree */
 2     private static class BTNode {
 3 
 4         public BTNode(int value) {
 5             this.value = value;
 6         }
 7 
 8         public int value;
 9         public BTNode left;
10         public BTNode right;
11     }
View Code

然后,要用根结点左侧连接左子树的最大值,即左子树的最右的子结点,同样,根结点的右侧要连接右子树的最左子结点。

代码如下:

 1 public static BTNode convertStub(BTNode root) {
 2         //base case
 3         if (root == null) {
 4             return null;
 5         }
 6         //operation on left child node
 7         if (root.left != null) {
 8             BTNode left = convertStub(root.left);
 9             while (left.right != null) {
10                 left = left.right;
11             }
12             left.right = root;
13             root.left = left;
14         }
15         //operation on right child node
16         if (root.right != null) {
17             BTNode right = convertStub(root.right);
18             while (right.left != null) {
19                 right = right.left;
20             }
21             right.left = root;
22             root.right = right;
23         }
24         return root;
25     }
View Code

所以,convertStub方法返回的是输入的二元搜索树的根结点,而此时的二元搜索树的结点已经转化成了双向链表的结构,所以,如果要找到该双向链表的头结点,需要一直向左移动找到它的最小值的结点,即头结点,形成升序的双向链表。或者一直向右移动,直到找到最大值的结点,从而形成降序的双向链表。因而,convert方法如下:

 1 public static BTNode convert(BTNode root) {
 2         if (root == null) {
 3             return null;
 4         }
 5         root = convertStub(root);
 6         while (root.left != null) {
 7             root = root.left;
 8         }
 9         return root;
10     }
View Code

3,请输出一个输入二叉树的镜像。

其实这道题目想要表达的意思是把二叉树中所有的结点的左右子树进行交换。

同样,利用上面的数据结构表达二叉树的结点,生成镜像的方法为:

 1         // exchange the left node and the right one of all nodes
 2     public static BTNode mirrorBTree(BTNode root) {
 3         if (root == null) {
 4             return null;
 5         }
 6         BTNode tmp = root.left;
 7         root.left = root.right;
 8         root.right = tmp;
 9         if (root.left != null) {
10             root.left = mirrorBTree(root.left);
11         }
12         if (root.right != null) {
13             root.right = mirrorBTree(root.right);
14         }
15         return root;
16     }
View Code

该方法首先交换了根结点的左右子树,然后将根结点的左右子树的子结点进行交换,如此递归进行。

4,判断一棵二叉树是否是另外一棵二叉树的子结构。

同样,利用上面的数据结构模拟二叉树的结点。可以想到,要想判断一棵二叉树是否是另外一棵二叉树的子结构,应该首先找到A树中是否有一个结点是否跟B树的头结点相等,然后判断两个结点下的所有结点是否相等。如果满足了这两个条件,其中上就可以判断B树是A树的子结构。当然,两个结点相等,在此我们只判断是否值相等即可。

首先,写了一个方法用于判断两个二叉树是否相等:

1     public static boolean isBTreeEqual(BTNode root1, BTNode root2) {
2         boolean result = false;
3         if (root1 != null && root2 != null) {
4             result = root1.value == root2.value
5                     && isBTreeEqual(root1.left, root2.left)
6                     && isBTreeEqual(root1.right, root2.right);
7         }
8         return result;
9     }
View Code

要是两个二叉树相等的话,那么二叉树中的所有结点值相等。就是上述方法。

然后,需要找到值相等的根结点,之后都有可能判断两个二叉树是否相等。否则就不会一棵二叉树是另外一棵的子结构。

方法如下:

 1     // root1 is a big binary tree, and root2 is a small one.
 2     public static boolean isChildTree(BTNode root1, BTNode root2) {
 3         boolean result = false;
 4                 //base case
 5         if (root1 == null) {
 6             return false;
 7         }
 8                 //base case
 9         if (root2 == null) {
10             return true;
11         }
12         BTNode tmp = root1;
13         // find the equal root node of a child tree of root1
14         while (tmp != null) {
15             if (tmp.value < root2.value) {
16                 tmp = tmp.right;
17             } else if (tmp.value > root2.value) {
18                 tmp = tmp.left;
19             } else {// the equal root node exists.
20                 break;
21             }
22 
23         }
24         if (tmp != null) {// the equal root node exists.
25             result = isBTreeEqual(tmp, root2);
26         }
27         return result;
28     }
View Code

5,判断一个序列是否是某棵二叉树的后序遍历(假设输入的序列每个结点的值都不相等)。

当初刚一看到这个题目的时候感觉很没有头绪。某棵二叉树???的后序遍历???

接下来才想明白,原因是要判断这个序列是否符合二叉树后序遍历所应满足的条件。想到此就豁然开朗了。那么后序遍历应该满足什么样的条件呢?原来是这样的,后序遍历的最后一个结点是根结点的值,这点毋庸置疑。然后,二叉树的左子树的值全部都小于根结点,右子树的值全部都大于根结点,使得后序遍历的结果中最后一个元素之前的元素满足一个条件,即一串连续的值全部都小于根结点,然后之后的直到最后一个结点之前,其值全部都大于根结点,因此,可以以此判断该序列是否是一个合法的后序遍历。所以,原理知道之后,代码就显得比较简单了,如下:

 1     public static boolean isPostSequenceValid(int[] sequence) {
 2         if (sequence == null) {
 3             return false;
 4         }
 5         return isPostSequenceValid(sequence, 0, sequence.length - 1);
 6     }
 7 
 8     public static boolean isPostSequenceValid(int[] sequence, int start, int end) {
 9         if (sequence == null) {
10             throw new RuntimeException("input can't be null");
11         }
12         if (start < 0 || end > sequence.length - 1) {
13             throw new IndexOutOfBoundsException();
14         }
15         if (start > end) {
16             throw new RuntimeException("invalid parameter, indexstart or end");
17         }
18         boolean result = true;
19         int value = sequence[end];
20         int index = 0;
21         for (; index <= end; index++) {
22             if (sequence[index] > value) {
23                 break;
24             }
25         }
26         int j = index;
27         for (; j <= end; j++) {
28             if (sequence[j] < value) {
29                 result = false;
30                 break;
31             }
32         }
33 
34         return result && isPostSequenceValid(sequence, 0, index - 1)
35                 && isPostSequenceValid(sequence, index, end);
36     }
View Code

6,输入一个字符串,打印出该字符串中字符的所有排列,如输入“abc"可得打印出"abc", "acb", "bac", "bca"……

求所有的排列,可以分成两步来进行:1,首先求得所有可能出现在第一个位置的字符,即把第一个字符和后面所有的字符交换。然后固定第一个字符,求后面所有字符的排列,然后在求后面所有字符的排列的时候,仍然采用以上的两步。因而,采用递归的方式似乎是个完美的选择。

因而,可以写出代码如下所示:

 1     public static void permutate(String s) {
 2         if (s == null || "".equals(s)) {
 3             return;
 4         }
 5         int count = s.length();
 6         char[] chs = s.toCharArray();
 7         permutate(chs, count);
 8     }
 9 
10     public static void permutate(char[] chs, int count) {
11         // base case
12         if (count == 1) {
13             System.out.println(chs);
14             return;
15         }
16         // loop with recursion
17         for (int i = 0; i < chs.length; i++) {
18             swap(chs, i, count - 1);
19             permutate(chs, count - 1);
20             swap(chs, i, count - 1);
21         }
22     }
23 
24     // swap the value of i and j.
25     public static void swap(char[] arr, int i, int j) {
26         char tmp = arr[i];
27         arr[i] = arr[j];
28         arr[j] = tmp;
29     }
View Code

其中含有两个参数的permutate是这个方法的核心,方法体内循环中用到了递归,还真不好理解,就先记得这样能够解决问题就行了吧。swap函数用交换char数组中两个位置的值。

好吧,边敲代码边写博客两个半小时了要,又累又困,就先写到这吧,以后有机会、有时间再继续……

原文地址:https://www.cnblogs.com/littlepanpc/p/3965238.html