前言
对于剑指offer中21-40的算法题的解题思路和代码总结。
题目21: 从上往下打印出二叉树的每个节点,同层节点从左至右打印。
思路
使用队列先进先出的原理实。
代码
import java.util.ArrayList;
import java.util.Queue;
import java.util.LinkedList;
/**
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> al = new ArrayList();
Queue<TreeNode> que=new LinkedList<TreeNode>();
if(root == null)
return al;
que.offer(root);
while(!que.isEmpty()){
TreeNode tn = que.poll();
al.add(tn.val);
if(tn.left != null){
que.offer(tn.left);
}
if(tn.right != null){
que.offer(tn.right);
}
}
return al;
}
}
题目22: 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
思路
因为后续遍历是先遍历左子树,再遍历右子树,最后遍历根节点,所以后序遍历序列的最后一个元素是根节点。序列前面小于根节点的部分属于左子树,后面大于根节点的部分属于右子树。如果后面部分中如果有元素小于根节点,则该序列不是二叉搜索树的后序遍历序列。
代码
public class Solution {
public boolean VerifySquenceOfBST(int [] sequence) {
if(sequence.length == 0){
return false;
}
return isss(sequence, 0, sequence.length-1);
}
public boolean isss(int [] sequence, int start, int end){
if(end <= start){
return true;
}
int current = start;
while(current < end && sequence[current] < sequence[end]){
current++;
}
int ind = current;
while(ind < end && sequence[ind] > sequence[end]){
ind++;
}
if(ind != end){
return false;
}
return isss(sequence, start, current-1)&&isss(sequence, current, end-1);
}
}
--> 题目23: 输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)
思路
代码
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>> paths = new ArrayList<ArrayList<Integer>>();
ArrayList<Integer> path = new ArrayList<Integer>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
if(root == null){
return paths;
}
path.add(root.val);
target = target - root.val;
if(target == 0 && root.left == null && root.right == null){
paths.add(new ArrayList<Integer>(path));
}
if(target > 0){
FindPath(root.left, target);
FindPath(root.right, target);
}
path.remove(path.size()-1);
return paths;
}
}
题目24: 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
思路
代码
/*
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 curHead = pHead;
while(curHead != null){
RandomListNode newRLN = new RandomListNode(curHead.label);
newRLN.next = curHead.next;
curHead.next = newRLN;
curHead = newRLN.next;
}
curHead = pHead;
while(curHead != null){
RandomListNode RLN = curHead.next;
if(curHead.random != null){
RLN.random = curHead.random.next;
}
curHead = RLN.next;
}
curHead = pHead;
RandomListNode res = curHead.next;
while(curHead.next != null){
RandomListNode RLN = curHead.next;
curHead.next = RLN.next;
curHead = RLN;
}
return res;
}
}
题目25: 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
思路
代码
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Sulotion {
TreeNode last = null;
public TreeNode Convert(TreeNode pRootOfTree) {
if (pRootOfTree == null)
return null;
ConvertNode(pRootOfTree);
while(pRootOfTree.left != null )
pRootOfTree = pRootOfTree.left;
return pRootOfTree;
}
public void ConvertNode(TreeNode curTree){
if (curTree.left != null)
ConvertNode(curTree.left);
curTree.left = last;
if(last != null)
last.right = curTree;
last = curTree;
if(curTree.right != null)
ConvertNode(curTree.right);
}
}
-->题目26: 输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字abc,acb,bac,bca,cab和cba。
思路
代码
import java.util.ArrayList;
import java.util.Collections;
public class Sulotion {
ArrayList<String> resL = new ArrayList<String>();
public ArrayList<String> Permutation(String str) {
char[] cs = str.toCharArray();
alCircumstances(0, cs);
Collections.sort(resL);
return resL;
}
public void alCircumstances(int index, char[] cs){
if(index == cs.length-1){
if(!resL.contains(String.valueOf(cs))){
resL.add(String.valueOf(cs));
}
}else{
for (int i = index; i < cs.length; i++){
exchange(i, index, cs);
alCircumstances(index+1, cs);
exchange(i, index, cs);
}
}
}
public void exchange(int i, int j, char[] cs){
char ex = cs[j];
cs[j] = cs[i];
cs[i] = ex;
}
}
题目27: 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
思路
代码
import java.util.HashMap;
import java.util.Map;
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
HashMap<Integer,Integer> cm = new HashMap<Integer, Integer>();
for(int i = 0; i < array.length; i++){
if(!cm.containsKey(array[i])){
cm.put(array[i], 1);
}else{
cm.put(array[i], cm.get(array[i]) + 1);
}
}
for(Map.Entry<Integer, Integer> a:cm.entrySet()){
if(a.getValue() > array.length/2){
return a.getKey();
}
}
return 0;
}
}
题目28: 输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
思路
代码
import java.util.ArrayList;
import java.util.Comparator;
import java.util.PriorityQueue;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> res = new ArrayList<Integer>();
if(k < 1 || input.length < k){
return res;
}
PriorityQueue<Integer> maxheap = new PriorityQueue<Integer>(k, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
for(int i = 0; i < input.length; i++){
if(maxheap.size() < k){
maxheap.offer(input[i]);
}else{
if(maxheap.peek() > input[i]){
maxheap.poll();
maxheap.offer(input[i]);
}
}
}
for(Integer i:maxheap){
res.add(i);
}
return res;
}
}
题目29: HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)
思路
代码
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
int mixsum = array[0];
int cursum = array[0];
for(int i = 1; i < array.length; i++){
if(cursum > 0){
cursum += array[i];
}else{
cursum = array[i];
}
if(cursum > mixsum){
mixsum = cursum;
}
}
return mixsum;
}
}
题目30: 求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
思路
代码
public class Solution {
public int NumberOf1Between1AndN_Solution(int n) {
int sum = 0;
for(int i = 1; i <= n; i++){
int hh = i;
while(hh != 0){
if(hh%10 == 1){
sum++;
}
hh /= 10;
}
}
return sum;
}
}
--> 题目31: 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
思路
冒泡排序,按照两两组合中更小的顺序排序。3,32可以组成332和323,323更小,排序顺序为32,3。
代码
public class Solution {
public String PrintMinNumber(int [] numbers) {
for(int i = 0; i < numbers.length-1; i++){
for(int j = 0; j < numbers.length-1-i; j++){
String a = String.valueOf(numbers[j]) + String.valueOf(numbers[j+1]);
String b = String.valueOf(numbers[j+1]) + String.valueOf(numbers[j]);
if(a.compareTo(b) > 0){
int h = numbers[j];
numbers[j] = numbers[j+1];
numbers[j+1] = h;
}
}
}
StringBuffer sb = new StringBuffer();
for(int i:numbers){
sb.append(String.valueOf(i));
}
return sb.toString();
}
}
题目32: 把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
思路
代码
// 解法1:简单但是效率较低
public class Solution {
public int GetUglyNumber_Solution(int index) {
int i = 0;
while(index != 0){
i++;
if(isUgly(i)){
index--;
}
}
return i;
}
public boolean isUgly(int number){
while(number % 2 == 0){
number /= 2;
}
while (number % 3 == 0){
number /= 3;
}
while (number % 5 == 0){
number /= 5;
}
if(number == 1){
return true;
}
return false;
}
}
// 解法2:更加高效但是复杂度高
public class Solution {
public int GetUglyNumber_Solution(int index) {
if(index == 0)
return 0;
int[] result = new int[index];
int index2 = 0, index3 = 0, index5 = 0, count = 1;
result[0] = 1;
while(count < index){
int f = min(result[index2]*2,min(result[index3]*3, result[index5]*5));
if(f == result[index2]*2)
index2++;
if(f == result[index3]*3)
index3++;
if(f == result[index5]*5)
index5++;
result[count] = f;
count++;
}
return result[index-1];
}
public int min(int a, int b){
return a > b ? b : a;
}
}
题目33: 在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).
思路
代码
import java.util.HashMap;
public class Solution {
public int FirstNotRepeatingChar(String str) {
if(str.length() == 0 || str == null){
return -1;
}
char[] strs = str.toCharArray();
HashMap<Character, Integer> hm = new HashMap<Character, Integer>();
for(int i = 0; i < strs.length; i++){
if(!hm.containsKey(strs[i])){
hm.put(strs[i], 1);
}else{
hm.put(strs[i], hm.get(strs[i]) + 1);
}
}
for(int i = 0; i < strs.length; i++){
if(hm.get(strs[i]) == 1){
return i;
}
}
return -1;
}
}
题目34: 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
思路
归并排序
代码
public class suanfa34 {
private int count = 0;
public int InversePairs(int [] array) {
if(array.length == 0 && array == null){
return 0;
}
int[] temp = new int[array.length];
mergesort(array, temp, 0, array.length - 1);
return count % 1000000007;
}
public void mergesort(int[] array, int[] temp, int start, int end){
if(start >= end){
return;
}
int mid = (start + end)>>1;
int start2 = mid + 1;
mergesort(array, temp, start, mid);
mergesort(array, temp, start2, end);
int realstart = start;
int tempindex = start;
while(start <= mid && start2 <= end){
if(array[start] > array[start2]){
temp[tempindex++] = array[start++];
count += end - start2 + 1;
}else{
temp[tempindex++] = array[start2++];
}
}
if(start <= mid){
System.arraycopy(array, start, temp, tempindex, mid - start + 1);
}
if(start2 <= end){
System.arraycopy(array, start2, temp, tempindex, end - start2 + 1);
}
System.arraycopy(temp, realstart, array, realstart, end - realstart + 1);
}
}
题目35: 输入两个链表,找出它们的第一个公共结点。
思路
代码
import 辅助.ListNode;
// 利用长度差
public class suanfa35 {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode l1 = pHead1;
ListNode l2 = pHead2;
while(l1 != null && l2 != null){
l1 = l1.next;
l2 = l2.next;
}
if(l1 != null){
while(l1 != null){
pHead1 = pHead1.next;
l1 = l1.next;
}
}else{
while(l2 != null){
pHead2 = pHead2.next;
l2 = l2.next;
}
}
while(pHead1!=null && pHead2 != null && pHead1.val != pHead2.val){
pHead1 = pHead1.next;
pHead2 = pHead2.next;
}
return pHead1;
}
}
// 利用栈
public class suanfa35_2 {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
Stack<ListNode> st1 = new Stack<ListNode>();
Stack<ListNode> st2 = new Stack<ListNode>();
while(pHead1 != null){
st1.push(pHead1);
pHead1 = pHead1.next;
}
while(pHead2 != null){
st2.push(pHead2);
pHead2 = pHead2.next;
}
ListNode result = null;
while(st1 != null && st2 != null && st1.peek().val == st2.peek().val){
result = st1.pop();
st2.pop();
}
return result;
}
}
题目36: 统计一个数字在排序数组中出现的次数。
思路
利用二分查找先查找到k所在位置,然后确定第一个k的位置和最后一个k的位置,相减就是k的个数
代码
public class Solution {
public int GetNumberOfK(int [] array , int k) {
if(array.length == 0 || array == null){
return 0;
}
int end = getk(array, 0, array.length-1, k);
if(end != -1){
int start = end;
while(start >= 0 && array[start] == array[end]){
start --;
}
while(end <= array.length-1 && array[start+1]==array[end]){
end ++;
}
return end - start - 1;
}else {
return 0;
}
}
public int getk(int[] array, int start, int end, int k){
if(start > end) {
return -1;
}
int mid = (start + end)/2;
if(array[mid] < k){
getk(array, mid+1, end, k);
}else if(array[mid] > k){
getk(array, start, mid-1, k);
}else{
return mid;
}
return -1;
}
}
-->题目37: 输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
思路
从元素版本到最精简版本体现了对这道题的理解深度。
代码
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
// 元素版本
public class Solution {
int deep = 1;
public int TreeDepth(TreeNode root) {
if(root == null){
return 0;
}
depth(root.left, 1);
depth(root.right, 1);
return deep;
}
public void depth(TreeNode root, int de){
if(root == null){
return;
}
de++;
if(de > deep){
deep = de;
}
depth(root.left, de);
depth(root.right, de);
}
}
// 精简版本
public class Sulotion {
int curdeep = 0;
int maxdeep = 0;
public int TreeDepth(TreeNode root) {
if(root == null){
return maxdeep;
}
curdeep++;
if(curdeep > maxdeep){
maxdeep = curdeep;
}
if(root.left != null)
TreeDepth(root.left);
if(root.right != null)
TreeDepth(root.right);
curdeep--;
return maxdeep;
}
}
// 最精简版本
public int TreeDepth(TreeNode root) {
if(root == null){
return 0;
}
int left = TreeDepth(root.left);
int right = TreeDepth(root.right);
return left > right ? left+1:right+1;
}
-->题目38: 输入一棵二叉树,判断该二叉树是否是平衡二叉树。
思路
代码
public class Solution {
private static boolean isBalanced = true;
public static boolean IsBalanced_Solution(TreeNode root) {
if(root == true){
return true;
}
getDepth(root);
return isBalanced;
}
public static int getDepth(TreeNode root) {
if(root==null)
return 0;
int left = getDepth(root.left);
int right = getDepth(root.right);
if(Math.abs(left-right)>1)
isBalanced = false;
}
题目39: 一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
思路
1、将数组中的所有元素进行亦或运算res,最后的结果肯定是那两个只出现了一次的数字(a和b)的亦或结果。参考以下公式。
1)a^a = 0
2)a^0 = a
3)a^b^c = a^(b^c) = (a^c)^b
4)a^c^d^c^t^d^t^b = a^b
2、res二进制表示的1的位置即为这个a和 b二进制表示中不同的部分,用res二进制表示中最右边的1的位置(p)来将数组分为两个,即p位置上为1和0的数字分到两个数组中,如上所述,a和b在p位置上的数字肯定不同,所以a和b肯定会被分别分到两个数组中,相同的数字的二进制表示在p位置上的表示必然是相同,所以相同的两个数字必然也会被分到同一个数组中。
3、对两个数组中的所有数字分别进行亦或运算,最后的结果便是a和b。
代码
//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
public class Solution {
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
int sum = 0;
for(int i = 0; i < array.length; i++){
sum ^= array[i];
}
int index = 0;
for(int i = 0; i < 32; i++){
if(((sum >> i) & 1) == 1){
index = i;
break;
}
}
num1[0] = 0;
num2[0] = 0;
for(int i = 0; i < array.length; i++){
if(((array[i] >> index) & 1) == 1){
num1[0] ^= array[i];
}else {
num2[0] ^= array[i];
}
}
}
}
题目40: 小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
思路
代码
import java.util.ArrayList;
public class Solution {
public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer>> als = new ArrayList<>();
int start = 1, end = 2;
while(start < end){
int curSum = (start + end) * (end - start + 1) / 2;
if(curSum == sum){
ArrayList<Integer> al = new ArrayList();
for(int i = start; i <= end; i++){
al.add(i);
}
als.add(al);
end++;
}else if(curSum < sum){
end++;
}else{
start++;
}
}
return als;
}
}