剑指Offer——编程题的Java实现(更新完毕……)

 

目录

二维数组中的查找

替换空格 

从尾到头打印链表

重建二叉树

用两个栈实现队列

用两个队列实现一个栈

旋转数组的最小数字

 斐波那契数列

跳台阶

变态跳台阶

矩形覆盖

 二进制中1的个数

数值的整数次方

调整数组顺序使奇数位于偶数前面

相对位置发生变化的解法

链表中倒数第k个结点

反转链表

合并两个排序的链表

树的子结构

顺时针打印矩阵

二叉树的镜像

包含min函数的栈

栈的压入、弹出序列

二叉搜索树的后序遍历序列

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

复杂链表的复制

连续子数组的最大和

字符串的排列

二叉树与双向链表

字符串的组合

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

把数组排成最小的数

第一个只出现一次的字符

数组中的逆序对

两个链表的第一个公共结点

数字在排序数组中出现的次数

二叉树的深度及判断是否为平衡二叉树

数组中只出现一次的数字

使用map

使用异或

和为S的连续正数序列

和为S的两个数字

左旋转字符串

翻转单词顺序列

扑克牌顺子

孩子们的游戏(圆圈中最后剩下的数)

约瑟夫环问题

递推公式

求1~n的和(不使用乘除)

不用加减乘除做加法

字符串转数字

数组中重复的数字

构建乘积数组

正则表达式匹配

字符流中第一个不重复的字符

链表中环的入口结点

删除链表中重复的结点

重复结点不保留

重复结点保留

表示数值的字符串

二叉树的下一个结点

对称的二叉树

把二叉树打印成多行

之字形打印二叉树

序列化二叉树

二叉搜索树的第k个结点

数据流中的中位数

滑动窗口的最大值


 

二维数组中的查找

在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

 

       /*
	 * 思路 矩阵是有序的,从右上角来看,向左数字递减,向下数字递增, 
	 * 因此从右上角开始查找,当要查找数字比左下角数字大时。下移
	 * 要查找数字比左上角数字小时,左移
	 */
	public class Solution {
		public boolean Find(int[][] array, int target) {
			int len = array.length - 1;
			int i = 0;
			while ((len >= 0) && (i < array[0].length)) {
				if (array[len][i] > target) {
					len--;
				} else if (array[len][i] < target) {
					i++;
				} else {
					return true;
				}
			}
			return false;
		}
	}
  • 替换空格 

请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

 public class Solution {  
            // 从后往前,先计算需要多少空间,然后从后往前移动,则每个字符只为移动一次.  
            public String replaceSpace(StringBuffer str) {  
                if (str == null) {  
                    return null;  
                }  
                int blankNum = 0;  
                int length = str.length();  
                int newLength = 0;  
                for (int i = 0; i < length; i++) {  
                    if (str.charAt(i) == ' ') {  
                        blankNum++;  
                    }  
                }  
                newLength = length + 2 * blankNum; // 替换后的字符串长度  
                char[] newChars = new char[newLength];// 新的字符数组  
                int index = newLength - 1;  
                for (int i = length - 1; i >= 0; i--) {  
                    if (str.charAt(i) == ' ') {  
                        newChars[index--] = '0';  
                        newChars[index--] = '2';  
                        newChars[index--] = '%';  
                    } else {  
                        newChars[index--] = str.charAt(i);  
                    }  
                }  
                return new String(newChars);  
            }  
        }  
    public class Solution {  
            //借助StringBuffer  
            public String replaceSpace(StringBuffer str) {  
                StringBuffer sb = new StringBuffer();  
                for (int i = 0; i < str.length(); i++) {  
                    if (str.charAt(i) == ' ') {  
                        sb.append("%20");  
                    } else {  
                        sb.append(str.charAt(i));  
                    }  
                }  
                return sb.toString();  
            }  
        }  
  • 从尾到头打印链表

输入一个链表,从尾到头打印链表每个节点的值。

链表结点定义:

public class ListNode {
	int val;
	ListNode next = null;

	ListNode(int val) {
		this.val = val;
	}
}

使用递归:
 
import java.util.ArrayList;
	public class Solution {
		ArrayList arrayList = new ArrayList();
		//使用递归实现
		public ArrayList printListFromTailToHead(ListNode listNode) {
			if (listNode != null) {
				printListFromTailToHead(listNode.next);
				arrayList.add(listNode.val);
			}
			return arrayList;
		}
	}

使用栈的后进先出
import java.util.ArrayList;
import java.util.Stack;

public class Solution {

	public ArrayList printListFromTailToHead(ListNode listNode) {

		Stack stack = new Stack<>();
		while (listNode != null) {
			stack.push(listNode.val);
			listNode = listNode.next;
		}

		ArrayList list = new ArrayList<>();
		while (!stack.isEmpty()) {
			list.add(stack.pop());
		}
		return list;
	}
}
  • 重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

/**
	 * Definition for binary tree
	 * public class TreeNode {
	 *     int val;
	 *     TreeNode left;
	 *     TreeNode right;
	 *     TreeNode(int x) { val = x; }
	 * }
	 */
	public class Solution {
		public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
			TreeNode root = reConstructBinaryTree(pre, 0, pre.length - 1, in, 0, in.length - 1);
			return root;
		}

		// 前序遍历{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}
		private TreeNode reConstructBinaryTree(int[] pre, int startPre, int endPre, int[] in, int startIn, int endIn) {

			if (startPre > endPre || startIn > endIn)
				return null;
			TreeNode root = new TreeNode(pre[startPre]);

			for (int i = startIn; i <= endIn; i++)
				if (in[i] == pre[startPre]) {
					root.left = reConstructBinaryTree(pre, startPre + 1, startPre + i - startIn, in, startIn, i - 1);
					root.right = reConstructBinaryTree(pre, i - startIn + startPre + 1, endPre, in, i + 1, endIn);
				}

			return root;
		}
	}

用两个栈实现队列

用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

有两个栈,栈1和栈2.当入栈的时候,我们将它全放进栈1中,当需要出栈的时候,我们将栈1出栈到栈2中,然后再将栈2依次出栈。所以入栈的时候,思路很简单;当需要出栈的时候,我们用API提供的方法while(stack1.isEmpty())来将所有栈1的元素压入栈2中,然后将栈2弹出就可以.

 

import java.util.Stack;

public class Solution {
	Stack stack1 = new Stack();
	Stack stack2 = new Stack();

	public void push(int node) {
		stack1.push(node);
	}

	public int pop() {
		if (stack1.empty() && stack2.empty()) {
			throw new RuntimeException("Queue is empty!");
		}
		if (stack2.empty()) {
			while (!stack1.empty()) {
				stack2.push(stack1.pop());
			}
		}
		return stack2.pop();
	}

}

用两个队列实现一个栈

移步

http://blog.csdn.net/mine_song/article/details/63322097

旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

使用二分查找:

 

public class Solution {
		public int minNumberInRotateArray(int[] array) {
			if (array == null || array.length == 0)
				return 0;
			int low = 0;
			int high = array.length - 1;
			while (low < high) {
				int mid = low + (high - low) / 2;
				if (array[mid] > array[high]) {
					low = mid + 1;
					// high = high - 1;可以避免low,high,mid相等的找不到最小值情况。
					// int[] array={1,0,1,1,1};
				} else if (array[mid] == array[high]) {
					high = high - 1;
				} else {
					high = mid;
				}
			}
			return array[low];
		}
	}

首先数组长度为零时,返回零,因为测试要求这样。然后有一个特殊情况是没有旋转,那么返回array[0],其次一般情况while一直循环,直到后面的数 < 前面的数停止,这个数就是我们要找的。

 

 

public class Solution {
		public int minNumberInRotateArray(int[] array) {
			if (array.length == 0)
				return 0;
			// 避免i+1越界,i要小于array.length - 1
			for (int i = 0; i < array.length - 1; i++) {
				if (array[i] > array[i + 1])
					return array[i + 1];
			}
			// 所有元素相等时候或者未旋转,返回array[0]
			return array[0];
		}
	}

 

 斐波那契数列

 

大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。n<=39

 

public class Solution {

		public int Fibonacci(int n) {
			// 方法:用递归,系统会让一个超大的n来让StackOverflow,所以递归就不考虑了
			// 使用迭代法,用fn1和fn2保存计算过程中的结果,并复用起来
			int fn1 = 1;
			int fn2 = 1;// 考虑出错情况
			int res = 0;
			if (n <= 0) {
				return 0;
			} // 第一和第二个数直接返回
			if (n == 1 || n == 2) {
				return 1;
			}
			for (int i = 3; i <= n; i++) {
				res = fn1 + fn2;
				fn2 = fn1;
				fn1 = res;
			}
			return res;
		}

	}

 

跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

 

public class Solution {
		public int JumpFloor(int target) {

			int fn1 = 1;
			int fn2 = 2;
			int res = 0;
			if (target <= 0) {
				return 0;
			}
			if (target == 1) {
				return 1;
			}
			if (target == 2) {
				return 2;
			}
			for (int i = 3; i <= target; i++) {
				res = fn1 + fn2;
				fn1 = fn2;
				fn2 = res;
			}
			return res;
		}
	}

递归

 

对于N级台阶,可以从N-1级和N-2级上来,所以JumpFloor(N) =  JumpFloor(N-1)+JumpFloor(N-2)
N=1时,只有一种
N=2时,有两种:一次2级;两次1级

 

 

 

 

public class Solution {

		public int JumpFloor(int target) {
			int result = 0;
			if (target > 0) {
				if (target <= 2)
					return target;
				else
					return result = JumpFloor(target - 1) + JumpFloor(target - 2);
			}
			return result;
		}

变态跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

:

非递归:

 

	public class Solution {
		public int JumpFloorII(int target) {
			int jumpFlo = 1;
			while (--target > 0) {
				jumpFlo *= 2;
			}
			return jumpFlo;

		}
	}


2^(n-1)可以用位移操作进行

 

 

	public class Solution {
		public int JumpFloorII(int target) {
			return  1<<--target;
		}
        }


使用递归:

 

 

public class Solution {
		public int JumpFloorII(int target) {
			if (target < 0) {
				return 0;
			} else if (target == 1) {
				return 1;
			} else {
				return 2 * JumpFloorII(target - 1);
			}
		}
	}

 

矩形覆盖

 

我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

同上述青蛙跳台阶

 二进制中1的个数

    输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

 如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。

  举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。

 

public class Solution {
	public int NumberOf1(int n) {
		int num = 0;
		while (n != 0) {
			n = n & (n - 1);
			num++;
		}
		return num;
	}
}

 

数值的整数次方

 

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方

 


public class Solution {
	//时间复杂度O(n)
	public double Power(double base, int exponent) {
		int n = Math.abs(exponent);
		if (n == 0)
			return 1;
		if (n == 1)
			return base;
		//以上两个if判断可省。for循环中判断
		double result = 1.0;
		for (int i = 0; i < n; i++) {
			result *= base;
		}
		if (exponent < 0) {
			result = 1 / result;
		}
		return result;
	}
}

使用递归,时间复杂度O(logn)
当n为偶数,a^n =(a^n/2)*(a^n/2)
当n为奇数,a^n = a^[(n-1)/2] * a^[(n-1)/2] * a
举例
2^11 = 2^1 * 2^2 * 2^8
2^1011 = 2^0001 * 2^0010 * 2^1000

 

 

public class Solution {
	// 时间复杂度O(lgn)
	public double power(double base, int exponent) {
		int n = Math.abs(exponent);
		double result = 0.0;
		if (n == 0)
			return 1.0;
		if (n == 1)
			return base;
		result = power(base, n >> 1);
		result *= result;
		// 如果指数n为奇数,则要再乘一次底数base
		// 最后一位是1,与1相与得1,是奇数
		if ((n & 1) == 1)
			result *= base;
		// 如果指数为负数,则应该求result的倒数
		if (exponent < 0)
			result = 1 / result;
		return result;
	}
}

调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

1.使用冒泡排序,前偶后奇就交换

 

public void reOrderArray(int[] array) {
		for (int i = 0; i < array.length - 1; i++) {
			for (int j = 0; j < array.length - 1 - i; j++) {
				// 前偶后奇数就交换
				if ((array[j] & 1) == 0 && (array[j + 1] & 1) == 1) {
					array[j] = array[j] ^ array[j + 1];
					array[j + 1] = array[j] ^ array[j + 1];
					array[j] = array[j] ^ array[j + 1];
				}
			}
		}
	}


2.空间换时间,使用额外数组。

 

 

public void reOrderArray(int[] array) {
		int[] newArr = new int[array.length];
		//newArr的下标计数器
		int j = 0;
		for (int i = 0; i < array.length; i++)
			if ((array[i] & 1) == 1) {
				newArr[j] = array[i];
				j++;
			}
		for (int i = 0; i < array.length; i++)
			if ((array[i] & 1) == 0) {
				newArr[j] = array[i];
				j++;
			}
		for (int i = 0; i < array.length; i++)
			array[i] = newArr[i];
	}

相对位置发生变化的解法

 

public class Solution {

	public void reOrderArray(int[] array) {
		if (array == null)
			return;
		int begin = 0;
		int end = array.length - 1;
		while (begin <= end) {
			while (begin <= end && ((array[begin] & 1) == 1))
				begin++;
			while (begin <= end && ((array[end] & 1) == 0))
				end--;
			if (begin <= end) {
				array[begin] = array[begin] ^ array[end];
				array[end] = array[begin] ^ array[end];
				array[begin] = array[begin] ^ array[end];
			}
		}
	}
}

链表中倒数第k个结点

输入一个链表,输出该链表中倒数第k个结点。

快慢指针,让快指针先走k步,然后慢指针开始走,若快指针走到末尾(为null),就是慢指针指向的就是倒数第k个结点

 

public class ListNode {
	int val;
	ListNode next = null;

	ListNode(int val) {
		this.val = val;
	}
}

 

 

public class Solution {
	public ListNode FindKthToTail(ListNode head, int k) {
		ListNode front = head;
		int i = 0;
		for (; front != null && i < k; i++) {
			front = front.next;

		}
		// 如果k大于链表的长度或者k小于0,返回null;
		if (i != k)
			return null;
		ListNode behind = head;
		while (front != null) {
			front = front.next;
			behind = behind.next;
		}
		// 若k等于0,则behind为null
		return behind;
	}
}

反转链表

输入一个链表,反转链表后,输出链表的所有元素。

 

public class ListNode {
	int val;
	ListNode next = null;

	ListNode(int val) {
		this.val = val;
	}
}

 

 

public class Solution {
	public ListNode ReverseList1(ListNode head) {
		if (head == null)
			return null;
		// head为当前节点,如果当前节点为空的话,那就什么也不做,直接返回null;
		ListNode pre = null;
		ListNode next = null;
		// 当前节点是head,pre为当前节点的前一节点,next为当前节点的下一节点
		// 需要pre和next的目的是让当前节点从pre->head->next1->next2变成pre<-head next1->next2
		// 即pre让节点可以反转所指方向,但反转之后如果不用next节点保存next1节点的话,此单链表就此断开了
		// 所以需要用到pre和next两个节点
		// 1->2->3->4->5
		// 1<-2<-3 4->5
		while (head != null) {
			// 做循环,如果当前节点不为空的话,始终执行此循环,此循环的目的就是让当前节点从指向next到指向pre 
			// 如此就可以做到反转链表的效果
			// 先用next保存head的下一个节点的信息,保证单链表不会因为失去head节点的原next节点而就此断裂
			next = head.next;
			// 保存完next,就可以让head从指向next变成指向pre了,代码如下
			head.next = pre;
			// head指向pre后,就继续依次反转下一个节点
			// 让pre,head,next依次向后移动一个节点,继续下一次的指针反转
			pre = head;
			head = next;
		}
		// 如果head为null的时候,pre就为最后一个节点了,但是链表已经反转完毕,pre就是反转后链表的第一个节点
		// 直接输出pre就是我们想要得到的反转后的链表
		return pre;
	}
}

 

public class Solution {
	public ListNode ReverseList(ListNode head) {
		if (head == null)
			return null;
		ListNode newhead = null;
		ListNode pre = null;
		ListNode pNext = null;
		ListNode pNode = head;
		while (pNode != null) {
			pNext = pNode.next;
			//最后一个头结点赋值给newHead
			if (pNext == null)
				newhead = pNode;
			pNode.next = pre;
			pre = pNode;
			pNode = pNext;
		}
		return newhead;

	}
}

 

合并两个排序的链表

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
    比较两个链表的首结点,哪个小的的结点则合并到第三个链表尾结点,并向前移动一个结点。
    步骤一结果会有一个链表先遍历结束,或者没有
    第三个链表尾结点指向剩余未遍历结束的链表
    返回第三个链表首结点

public ListNode Merge(ListNode list1, ListNode list2) {
		if (list1 == null)
			return list2;
		if (list2 == null)
			return list1;
		//新建一个头节点,用来存合并的链表。
		ListNode newList = new ListNode(-1);
		ListNode temp = newList;
		while (list1 != null && list2 != null) {
			if (list1.val <= list2.val) {
				temp.next = list1;
				list1 = list1.next;
				temp = temp.next;

			} else {
				temp.next = list2;
				list2 = list2.next;
				temp = temp.next;
			}

		}
		//把未结束的链表连接到合并后的链表尾部
		if (list1 != null) {
			temp.next = list1;
		}
		if (list2 != null) {
			temp.next = list2;
		}
		return newList.next;
	}

递归:

 

 

public ListNode Merge(ListNode list1, ListNode list2) {
		if (list1 == null)
			return list2;
		if (list2 == null)
			return list1;
		if (list1.val <= list2.val) {
			list1.next = Merge(list1.next, list2);
			return list1;
		} else {
			list2.next = Merge(list1, list2.next);
			return list2;
		}
	}

 

树的子结构

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

 思路:参考剑指offer
   1、首先设置标志位result = false,因为一旦匹配成功result就设为true,剩下的代码不会执行,如果匹配不成功,默认返回false

   2、递归思想,如果根节点相同则递归调用DoesTree1HaveTree2(),如果根节点不相同,则判断tree1的左子树和tree2是否相同,再判断右子树和tree2是否相同

3、注意null的条件,HasSubTree中,如果两棵树都不为空才进行判断,DoesTree1HasTree2中,如果Tree2为空,则说明第二棵树遍历完了,即匹配成功,tree1为空有两种情况:

(1)如果tree1为空&&tree2不为空说明不匹配,

(2) 如果tree1为空,tree2为空,说明匹配。

 

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

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

 

 

public class Solution {

	public boolean HasSubtree(TreeNode root1, TreeNode root2) {
		boolean result = false;
		// 当Tree1和Tree2都不为零的时候,才进行比较。否则直接返回false
		if (root2 != null && root1 != null) {
			// 如果找到了对应Tree2的根节点的点
			if (root1.val == root2.val) {
				// 以这个根节点为为起点判断是否包含Tree2
				result = doesTree1HaveTree2(root1, root2);
			}
			// 如果找不到,那么就再去root的左儿子当作起点,去判断时候包含Tree2
			if (!result) {
				result = HasSubtree(root1.left, root2);
			}
			// 如果还找不到,那么就再去root的右儿子当作起点,去判断时候包含Tree2
			if (!result) {
				result = HasSubtree(root1.right, root2);
			}
		}
		// 返回结果
		return result;
	}

	public boolean doesTree1HaveTree2(TreeNode root1, TreeNode root2) {
		// 如果Tree2已经遍历完了都能对应的上,返回true
		if (root2 == null) {
			return true;
		}
		// 如果Tree2还没有遍历完,Tree1却遍历完了。返回false
		if (root1 == null) {
			return false;
		}
		// 如果其中有一个点没有对应上,返回false
		if (root1.val != root2.val) {
			return false;
		}
		// 如果根节点对应的上,那么就分别去子节点里面匹配
		return doesTree1HaveTree2(root1.left, root2.left) && 
				doesTree1HaveTree2(root1.right, root2.right);
	}
}

 

顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

第一种方法:

 

public ArrayList printMatrix(int[][] array) {
		ArrayList result = new ArrayList();
		if (array.length == 0)
			return result;
		int n = array.length, m = array[0].length;
		if (m == 0)
			return result;
		// 此种方法关键点--求圈数
		int layers = (Math.min(n, m) - 1) / 2 + 1;
		// 要打印的圈数
		for (int i = 0; i < layers; i++) {
			// 打印每圈
			for (int k = i; k < m - i; k++)
				result.add(array[i][k]);// 左至右
			for (int j = i + 1; j < n - i; j++)
				result.add(array[j][m - i - 1]);// 右上至右下
			// 注意k,j开始的下标
			for (int k = m - i - 2; (k >= i) && (n - i - 1 != i); k--)
				result.add(array[n - i - 1][k]);// 右至左
			for (int j = n - i - 2; (j > i) && (m - i - 1 != i); j--)
				result.add(array[j][i]);// 左下至左上
		}
		return result;
	}


第二种方法:剑指offer

 

 

public ArrayList printMatrix(int[][] matrix) {
			// 得到矩阵的长度和宽度
			int rows = matrix.length;
			int columns = matrix[0].length;
			ArrayList list = new ArrayList();
			if (rows == 0 && columns == 0) {
				return list;
			}
			// start标志着每一次遍历一圈的起始下标
			int start = 0;
			while (rows > start * 2 && columns > start * 2) {
				printNumber(list, matrix, rows, columns, start);
				start++;
			}
			return list;
		}

		public ArrayList printNumber(ArrayList list, int[][] matrix, int rows, int columns,
				int start) {
			// 先打印行,从左到右
			for (int i = start; i <= columns - start - 1; i++) {
				list.add(matrix[start][i]);
			}
			// 打印列,从上到下
			for (int j = start + 1; j <= rows - start - 1; j++) {
				list.add(matrix[j][columns - start - 1]);
			}
			// 打印行,从左到右打印
			for (int m = columns - start - 2; m >= start && rows - start - 1 > start; m--) {
				list.add(matrix[rows - start - 1][m]);
			}
			// 打印列,从下到上,columns-start-1>start 避免当只有一列时重新打印
			for (int k = rows - start - 2; k >= start + 1 && columns - start - 1 > start; k--) {
				list.add(matrix[k][start]);
			}
			return list;
		}
	}

 

二叉树的镜像

操作给定的二叉树,将其变换为源二叉树的镜像。

二叉树的镜像定义:源二叉树 
    	    8
    	   /  \
    	  6   10
    	 / \  / \
    	5  7 9 11
    	镜像二叉树
    	    8
    	   /  \
    	  10   6
    	 / \  / \
    	11 9 7  5
public class Solution {
	public void Mirror(TreeNode root) {
		if (root == null) {
			return;
		}
		Stack stack = new Stack<>();
		stack.push(root);
		while (!stack.isEmpty()) {
			TreeNode node = stack.pop();
			//左右子树不空交换
			if (node.left != null || node.right != null) {
				TreeNode temp = node.left;
				node.left = node.right;
				node.right = temp;
			}

			if (node.left != null)
				stack.push(node.left);
			if (node.right != null)
				stack.push(node.right);
		}
	}
}

递归,和栈相似,用栈操作也可以用递归操作

 

 

public class Solution {
		// 二叉树的镜像如果二叉树为空,直接返回。
		// 如果二叉树左右子数都为空,也直接返回。
		// 把根节点的左右子树交换。
		// 如果根节点左子树不为空,递归调用镜像函数
		// 如果根节点右子树不为空,递归调用镜像函数
		public void Mirror(TreeNode root) {
			if (root == null)
				return;
			if (root.left == null && root.right == null)
				return;
			TreeNode pTemp = root.left;
			root.left = root.right;
			root.right = pTemp;
			if (root.left != null)
				Mirror(root.left);
			if (root.right != null)
				Mirror(root.right);
		}
	}

 

包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数。

 

         /*
	 * 思路:用一个栈stack保存数据,用另外一个栈min保存依次入栈最小的数 
	 * 比如,stack中依次入栈,5, 4, 3, 8, 10,11,12,1 
	 * 则min依次入栈,5, 4, 3, 3, 3, 3, 3, 1
	 * 每次入栈的时候,如果入栈的元素比min中的栈顶元素小或等于则入栈,否则入stack的栈顶元素。 
	 * 保持stack中和min中保持相同个数的元素
	 */
	Stack stack = new Stack<>();
	Stack minStack = new Stack<>();

	public void push(int node) {
		stack.push(node);
		// 如果min为空或者node比min栈中的元素小,则入min栈
		if (minStack.size() == 0 || minStack.peek() > node) {
			minStack.push(node);
		}
		// 否则把min栈中的顶部元素入栈
		else minStack.push(minStack.peek());
	}

	public void pop() {
		if (!stack.isEmpty()) {
			stack.pop();
			minStack.pop();
		}
	}

	public int top() {
		return stack.peek();
	}

	public int min() {
		return minStack.peek();
	}

栈的压入、弹出序列

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

借用一个辅助的栈,遍历压栈顺序,先讲第一个放入栈中,这里是1,然后判断栈顶元素是不是出栈顺序的第一个元素,这里是4,很显然1≠4,所以我们继续压栈,直到相等以后开始出栈,出栈一个元素,则将出栈顺序向后移动一位,直到不相等,这样循环等压栈顺序遍历完成,如果辅助栈还不为空,说明弹出序列不是该栈的弹出顺序。
  举例:
  入栈1,2,3,4,5
  出栈4,5,3,2,1
  首先1入辅助栈,此时栈顶1≠4,继续入栈2
  此时栈顶2≠4,继续入栈3
  此时栈顶3≠4,继续入栈4
  此时栈顶4=4,出栈4,弹出序列向后一位,此时为5,,辅助栈里面是1,2,3
  此时栈顶3≠5,继续入栈5
  此时栈顶5=5,出栈5,弹出序列向后一位,此时为3,,辅助栈里面是1,2,3
  ….
  依次执行,最后辅助栈为空。如果不为空说明弹出序列不是该栈的弹出顺序。

import java.util.Stack;
public class Solution {
	public boolean IsPopOrder(int[] pushA, int[] popA) {
		if (pushA == null || popA == null || pushA.length == 0 || popA.length == 0)
			return false;
		// 用于标识弹出序列的位置
		int index = 0;
		Stack stack = new Stack<>();
		for (int i = 0; i < pushA.length; i++) {
			stack.push(pushA[i]);
			// 如果栈不为空,且栈顶元素等于弹出序列
			while (!stack.isEmpty()) {
				if (stack.peek() != popA[index])
					break;
				stack.pop();
				index++;
			}
		}
		return stack.isEmpty();
	}
}

二叉搜索树的后序遍历序列

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

已知条件:后序序列最后一个值为root;二叉搜索树左子树值都比root小,右子树值都比root大。
  1、确定root;
  2、遍历序列(除去root结点),找到第一个大于root的位置,则该位置左边为左子树,右边为右子树;
  3、遍历右子树,若发现有小于root的值,则直接返回false;
  4、分别判断左子树和右子树是否仍是二叉搜索树(即递归步骤1、2、3)。

 

 

 

public boolean VerifySquenceOfBST(int[] sequence) {
		if (sequence == null || sequence.length == 0)
			return false;
		// 调用函数,java没有指针,要用下标模拟指针,新建函数判断
		return IsTreeBST(sequence, 0, sequence.length - 1);
	}

	private boolean IsTreeBST(int[] sequence, int start, int end) {
		// index是指示找到第一个大于左子树的结点
		int index = start;
		for (; index < end; index++)
			if (sequence[index] > sequence[end])
				break;
		// 若右子树有小于跟结点的值,返回false
		for (int i = index; i < end; i++)
			if (sequence[i] < sequence[end])
				return false;
		return IsTreeBST(sequence, start, index - 1) && IsTreeBST(sequence, index, end - 1);
	}

非递归:

 

// 非递归 
	// 非递归也是一个基于递归的思想:
	// 左子树一定比右子树小,因此去掉根后,数字分为left,right两部分,right部分的
	// 最后一个数字是右子树的根他也比左子树所有值大,因此我们可以每次只看有子树是否符合条件即可,
	// 即使到达了左子树左子树也可以看出由左右子树组成的树还想右子树那样处理 
	// 对于左子树回到了原问题,对于右子树,左子树的所有值都比右子树的根小可以暂时把他看出右子树的左子树
	// 只需看看右子树的右子树是否符合要求即可
	public boolean VerifySquenceOfBST(int[] sequence) {
		if (sequence == null || sequence.length == 0)
			return false;
		int len = sequence.length;
		while (--len > 0) {
			int i = 0;
			while (sequence[i] < sequence[len])
				i++;
			while (sequence[i] > sequence[len])
				i++;
			if (i != len)
				return false;

		}
		return true;
	}

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

输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。

 

import java.util.ArrayList;
public class Solution {
	private ArrayList> listAll = new ArrayList>();
	private ArrayList list = new ArrayList();

	public ArrayList> FindPath(TreeNode root, int target) {
		if (root == null)
			return listAll;
		list.add(root.val);
		target -= root.val;
		if (target == 0 && root.left == null && root.right == null)
			listAll.add(new ArrayList(list));
		/*
		 * if (root.left != null) { FindPath(root.left, target);
		 * list.remove(list.size() - 1); } if (root.right != null) {
		 * FindPath(root.right, target); list.remove(list.size() - 1); }
		 */
		// 继续遍历左右结点
		FindPath(root.left, target);
		FindPath(root.right, target);
		// 在返回父节点之前,在路径上删除该结点
		list.remove(list.size() - 1);
		return listAll;
	}
}

复杂链表的复制

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

 

	         /**解题思路:
		 * *1、遍历链表,复制每个结点,如复制结点A得到A1,将结点A1插到结点A后面;
		 * *2、重新遍历链表,复制老结点的随机指针给新结点,
		 * 如A1.random = A.random.next;
		 * 3、拆分链表,将链表拆分为原链表和复制后的链表
		 * */
	public RandomListNode Clone(RandomListNode pHead) {
		if (pHead == null)
			return null;
		RandomListNode pCur = pHead;
		// 复制next 如原来是A->B->C 变成A->A'->B->B'->C->C'
		while (pCur != null) {
			RandomListNode node = new RandomListNode(pCur.label);
			node.next = pCur.next;
			pCur.next = node;
			pCur = node.next;
		}
		pCur = pHead;
		// 复制random pCur是原来链表的结点 pCur.next是复制pCur的结点
		while (pCur != null) {
			if (pCur.random != null)
				pCur.next.random = pCur.random.next;
			pCur = pCur.next.next;
		}
		RandomListNode pCloneHead = null;
		RandomListNode pCloneNode = null;
		pCur = pHead;
		// 初始化,让pcur指向pCloneNode的下一个结点,避免空指针
		if (pCur != null) {
			pCloneHead = pCloneNode = pCur.next;
			pCur.next = pCloneNode.next;
			pCur = pCur.next;
			// pCur = pCloneNode.next;
		}
		while (pCur != null) {
			pCloneNode.next = pCur.next;
			pCloneNode = pCloneNode.next;
			pCur.next = pCloneNode.next;
			pCur = pCur.next;
		}
		return pCloneHead;
	}

连续子数组的最大和

HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。你会不会被他忽悠住?(子向量的长度至少是1)

 

/*
	算法时间复杂度O(n)
	用total记录累计值,maxSum记录和最大
	基于思想:对于一个数A,若是A的左边累计数非负,那么加上A能使得值不小于A,认为累计值对
	          整体和是有贡献的。如果前几项累计值负数,则认为有害于总和,total记录当前值。
	此时 若和大于maxSum 则用maxSum记录下来
*/
public class Solution {
	public int FindGreatestSumOfSubArray(int[] array) {
		if (array.length == 0)
			return 0;
		else {
			int total = array[0], maxSum = array[0];
			for (int i = 1; i < array.length; i++) {
				if (total >= 0)
					total += array[i];
				else
					total = array[i];
				if (total > maxSum)
					maxSum = total;
			}
			return maxSum;
		}
	}
}

 

 

public class Solution {
	public int FindGreatestSumOfSubArray(int[] array) {
		if (array.length == 0)
			return 0;
		int res = Integer.MIN_VALUE; // 记录当前所有子数组的和的最大值
		int tempMax = 0; // 包含array[i]的连续数组最大值
		for (int i = 0; i < array.length; i++) {
			tempMax = Math.max(tempMax + array[i], array[i]);
			res = Math.max(tempMax, res);
		}
		return res;
	}
}

字符串的排列

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,  则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

/**
	 * 对于无重复值的情况** 固定第一个字符,递归取得首位后面的各种字符串组合;*
	 * 再把第一个字符与后面每一个字符交换,并同样递归获得首位后面的字符串组合;
	 * *递归的出口,就是只剩一个字符的时候,递归的循环过程,就是从每个子串的第二个字符开始依次与第一个字符交换,然后继续处理子串。* *
	 * 假如有重复值呢?*
	 * *由于全排列就是从第一个数字起,每个数分别与它后面的数字交换,我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这两个数就不交换了。 *
	 * 例如abb,第一个数与后面两个数交换得bab,bba。然后abb中第二个数和第三个数相同,就不用交换了。* 但是对bab,第二个数和第三个数不
	 * 同,则需要交换,得到bba。* 由于这里的bba和开始第一个数与第三个数交换的结果相同了,因此这个方法不行。**
	 * 换种思维,对abb,第一个数a与第二个数b交换得到bab,然后考虑第一个数与第三个数交换,此时由于第三个数等于第二个数,*
	 * 所以第一个数就不再用与第三个数交换了。再考虑bab,它的第二个数与第三个数交换可以解决bba。此时全排列生成完毕!** * @param
	 */
	public ArrayList Permutation(String str) {
		ArrayList list = new ArrayList();
		if (str != null && str.length() > 0) {
			PermutationHelper(str.toCharArray(), 0, list);
			// 按照字典序输出
			Collections.sort(list);
		}
		return list;
	}
	private void PermutationHelper(char[] chars, int i, ArrayList list) {
		if (i == chars.length) {
			list.add(String.valueOf(chars));
			return;
		}
		// 用于结果去重,可以用list
		Set charSet = new HashSet();
		for (int j = i; j < chars.length; ++j) {
			if (!charSet.contains(chars[j])) {
				charSet.add(chars[j]);
				swap(chars, i, j);
				PermutationHelper(chars, i + 1, list);
				swap(chars, i, j);
			}
		}
	}
	private void swap(char[] cs, int i, int j) {
		char temp = cs[i];
		cs[i] = cs[j];
		cs[j] = temp;
	}

二叉树与双向链表

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

 

// 1.核心是中序遍历的非递归算法。
	//2.修改当前遍历节点与前一遍历节点的指针指向。
	public TreeNode ConvertBSTToBiList(TreeNode root)

	{
		if (root == null)
			return null;
		Stack stack = new Stack();
		TreeNode p = root;
		// 用于保存中序遍历序列的上一节点
		TreeNode pre = null;
		boolean isFirst = true;
		while (p != null || !stack.isEmpty()) {
			while (p != null) {
				stack.push(p);
				p = p.left;
			}
			p = stack.pop();
			if (isFirst) {
				// 将中序遍历序列中的第一个节点记为root
				root = p;
				pre = root;
				isFirst = false;
			} else {
				pre.right = p;
				p.left = pre;
				pre = p;
			}
			p = p.right;
		}
		return root;
	}

 

// 直接用中序遍历
	TreeNode head = null;
	TreeNode realHead = null;

	public TreeNode Convert(TreeNode pRootOfTree) {
		ConvertSub(pRootOfTree);
		return realHead;
	}

	private void ConvertSub(TreeNode pRootOfTree) {
		if (pRootOfTree == null)
			return;
		ConvertSub(pRootOfTree.left);
		if (head == null) {
			head = pRootOfTree;
			realHead = pRootOfTree;
		} else {
			head.right = pRootOfTree;
			pRootOfTree.left = head;
			head = pRootOfTree;
		}
		ConvertSub(pRootOfTree.right);
	}

 

字符串的组合

给一个字符串,比如ABC, 把所有的组合,即:A, B, C, AB, AC, BC, ABC, 都找出来。

假设我们想在长度为n的字符串中求m个字符的组合。我们先从头扫描字符串的第一个字符。针对第一个字符,我们有两种选择:一是把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选取m-1个字符;二是不把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选择m个字符。这两种选择都很容易用递归实现。

 

import java.util.*;
public class Solution {
	static List retList = new ArrayList<>();

	public static void combiantion(char chs[]) {
		if (chs.length == 0)
			return;
		Stack stack = new Stack();
		for (int i = 1; i <= chs.length; i++) {
			combine(chs, 0, i, stack);
		}
	}

	private static void combine(char[] chs, int begin, int number, Stack stack) {
		if (number == 0 && !retList.contains(stack.toString())) {
			retList.add(stack.toString());
			return;
		}
		if (begin == chs.length) {
			return;
		}
		stack.push(chs[begin]);
		combine(chs, begin + 1, number - 1, stack);
		stack.pop();
		combine(chs, begin + 1, number, stack);
	}
}

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

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

 

      /*
	 * 采用阵地攻守的思想:第一个数字作为第一个士兵,守阵地;times = 1;
	 * 遇到相同元素,times++;遇到不相同元素,即为敌人,同归于尽,times--;
	 * 当遇到times为0的情况,又以新的i值作为守阵地的士兵,继续下去, 到最后还留在阵地上的士兵,有可能是主元素。
	 * 再加一次循环,记录这个士兵的个数看是否大于数组一般即可。
	 */
	public  int MoreThanHalfNum_Solution(int[] array) {
		if (array.length <= 0)
			return 0;
		int res = array[0];
		int times = 1;
		for (int i = 1; i < array.length; i++) {
			if (times == 0) {
				res = array[i];
				times = 1;
			} else if (array[i] == res)
				times++;
			else
				times--;
		}

		times = 0;
		for (int i = 0; i < array.length; i++)
			if (res == array[i])
				times++;
		if (times * 2 > array.length)
			return res;
		else
			return 0;
	}
        // 数组排序后,如果符合条件的数存在,则一定是数组中间那个数
	// 要注意此数的出现的次数大于数组长度一半才可以
	// 涉及到快排sort,其时间复杂度为O(NlogN)
	public  int MoreThanHalfNum_Solution(int[] array) {
		Arrays.sort(array);
		int count = 0;
		int len = array.length;
		// 统计array[len / 2]出现的次数
		for (int i = 0; i < len; i++)
			if (array[i] == array[len / 2])
				count++;
		return count > len / 2 ? array[len / 2] : 0;
	}
       //基于快速排序的思想
	public int MoreThanHalfNum_Solution(int[] array) {
		if (array.length <= 0)
			return 0;
		int mid = array.length >> 1;
		int start = 0;
		int end = array.length - 1;
		int index = Partition(array, start, end);
		while (index != mid) {
			if (index > mid)
				index = Partition(array, start, index - 1);
			else
				index = Partition(array, index + 1, end);
		}
		int res = array[mid];
		int times = 0;
		for (int i = 0; i < array.length; i++)
			if (res == array[i])
				times++;
		if (times * 2 > array.length)
			return res;
		else
			return 0;
	}

	private int Partition(int[] arr, int start, int end) {
		// arr[start]为挖的第一个坑
		int key = arr[start];
		while (start < end) {
			while (arr[end] >= key && end > start)
				end--;
			arr[start] = arr[end];
			while (arr[start] <= key && end > start)
				start++;
			arr[end] = arr[start];
		}
		arr[start] = key;
		return start;
	}

把数组排成最小的数

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

解题思路: 先将整型数组转换成String数组,然后将String数组排序,最后将排好序的字符串数组拼接出来。关键就是制定排序规则。*排序规则如下:* 若ab > ba 则 a > b,* 若ab < ba 则 a < b,* 若ab = ba 则 a = b;

* 解释说明:* 比如 "3" < "31"但是 "331" > "313",所以要将二者拼接起来进行比较

import java.util.Arrays;
import java.util.Comparator;
public class Solution {
	// 使用String.valueOf(int)来变换整型到字符串
	// 使用StringBuilder来拼接字符串
	public String PrintMinNumber(int[] numbers) {
		if (numbers == null || numbers.length == 0)
			return "";
		int len = numbers.length;
		String[] str = new String[len];
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < len; i++) {
			str[i] = String.valueOf(numbers[i]);
		}
		// comparator 外部比较器
		Arrays.sort(str, new Comparator() {
			@Override
			public int compare(String s1, String s2) {
				String c1 = s1 + s2;
				String c2 = s2 + s1;
				return c1.compareTo(c2);
			}
		});
		for (int i = 0; i < len; i++) {
			sb.append(str[i]);
		}
		return sb.toString();
	}
}

附录String.compareTo源码 基于JDK 1.7

 

	// jdk1.7 实现value为String封装数组
	private final char value[];

	public int compareTo(String anotherString) {
	int len1 = value.length;
	int len2 = anotherString.value.length; 
	// 获取到两个字符串的较短的长度
	int lim = Math.min(len1, len2);
	char v1[] = value;
	char v2[] = anotherString.value;

	int k = 0;
	while (k < lim) {
		char c1 = v1[k];
		char c2 = v2[k];
		// 如果两个字符的ASC不相同,则直接返回
		if (c1 != c2) {
			return c1 - c2;
		}
		k++;
	}
	// 如果都一样,返回两个字符串的长度查
	return len1 - len2;
}

第一个只出现一次的字符

在一个字符串(1<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置

 

import java.util.LinkedHashMap;
public class Solution {
	// 使用map存储,key为字符,value为出现的次数
	//扫描map,取第一个value为1的key,返回下标
	public int FirstNotRepeatingChar(String str) {
		// 使用 linkedhashmap保持有序
		LinkedHashMap map = new LinkedHashMap<>();
		for (int i = 0; i < str.length(); i++)
			if (!map.containsKey(str.charAt(i)))
				map.put(str.charAt(i), 1);
			else
				map.put(str.charAt(i), map.get(str.charAt(i)) + 1);

		for (int index = 0; index < str.length(); index++)
			if (map.get(str.charAt(index)) == 1)
				return index;

		return -1;
	}

数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。
    即输出P%1000000007
输入描述:
题目保证输入的数组中没有的相同的数字数据范围:    
对于%50的数据,size<=10^4    
对于%75的数据,size<=10^5    
对于%100的数据,size<=2*10^5
输入例子:
1,2,3,4,5,6,7,0
输出例子:
7

使用归并排序

 

public class Solution {
	public int InversePairs(int[] array) {
		if (array == null || array.length == 0) {
			return 0;
		}
		int[] copy = new int[array.length];
		// for复制???可省
		for (int i = 0; i < array.length; i++) {
			copy[i] = array[i];
		}
		// 数值过大求余
		int count = InversePairsCore(array, copy, 0, array.length - 1);
		return count;
	}

	private int InversePairsCore(int[] array, int[] copy, int low, int high) {
		if (low == high) {
			return 0;
		}
		// mid属于前半部分最后一个数字
		int mid = (low + high) >> 1;
		int leftCount = InversePairsCore(array, copy, low, mid) % 1000000007;
		int rightCount = InversePairsCore(array, copy, mid + 1, high) % 1000000007;
		int count = 0;
		// i初始前半部分最后一个数字
		int i = mid;
		// j初始后半部分最后一个数字
		int j = high;
		// indexCopy记录copy数组的下标
		int locCopy = high;
		while (i >= low && j > mid) {
			if (array[i] > array[j]) {
				// j-mid~j的数都小于等于j的(排序),j的数字小于i
				count += j - mid;
				copy[locCopy--] = array[i--];
				if (count >= 1000000007)// 数值过大求余
					count %= 1000000007;
			} else {
				copy[locCopy--] = array[j--];
			}
		}
		for (; i >= low; i--)

		{
			copy[locCopy--] = array[i];
		}
		// 剩余部分依次放入临时数组
		for (; j > mid; j--) {
			copy[locCopy--] = array[j];
		}
		for (int s = low; s <= high; s++) {
			array[s] = copy[s];
		}
		return (leftCount + rightCount + count) % 1000000007;
	}
}

 

两个链表的第一个公共结点

 

输入两个链表,找出它们的第一个公共结点。

 

找出2个链表的长度,然后让长的先走两个链表的长度差,然后再一起走(因为2个链表用公共的尾部)
public class Solution {
	public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
		int len1 = findListLenth(pHead1);
		int len2 = findListLenth(pHead2);
		ListNode current1 = pHead1;
		ListNode current2 = pHead2;
		// 先在长链表上走上几步,在同时在两个链表上遍历
		if (len1 - len2 > 0)
			current1 = walkStep(current1, len1 - len2);
		else
			current2 = walkStep(current2, len2 - len1);
		while (current1 != null && current2 != null) {
			if (current1 == current2)
				return current1;
			current1 = current1.next;
			current2 = current2.next;
		}
		return null;
	}

	private ListNode walkStep(ListNode cur, int step) {
		// 从step~1
		while (step-- > 0)
			cur = cur.next;
		return cur;
	}

	// 计算链表长度
	private int findListLenth(ListNode head) {
		if (head == null)
			return 0;
		ListNode cur = head;
		int length = 0;
		while (cur != null) {
			length++;
			cur = cur.next;
		}
		return length;
	}
}

 

数字在排序数组中出现的次数

统计一个数字在排序数组中出现的次数。(二分查找找到第一个和最后一个,排序,首先想到二分查找)

 

public class Solution {
	public int GetNumberOfK(int[] array, int k) {
		int first, last;
		first = getFirstKIndex(array, 0, array.length - 1, k);
		last = getLastIndex(array, 0, array.length - 1, k);
		if (first > -1 && last > -1)
			return last - first + 1;
		return 0;
	}
	private int getLastIndex(int[] array, int start, int end, int k) {
		int mid;
		// 一定要等有=!!!!!!
		while (start <= end) {
			mid = (start + end) / 2;
			if (k == array[mid]) {
				// k在中间或者结尾,找到
				if (mid == end || array[mid + 1] != k) {
					return mid;
				} else {
					start = mid + 1;
				}
			} else if (k < array[mid]) {
				end = mid - 1;
			} else {
				start = mid + 1;
			}
		}
		return -1;
	}

	private int getFirstKIndex(int[] array, int start, int end, int k) {
		int mid;
		// 一定要等有=!!!!!!
		while (start <= end) {
			mid = (start + end) / 2;
			if (k == array[mid]) {
				if (mid == start || array[mid - 1] != k) {
					return mid;
				} else {
					end = mid - 1;
				}
			} else if (k < array[mid]) {
				end = mid - 1;
			} else {
				start = mid + 1;
			}
		}
		return -1;
	}
}

 

二叉树的深度及判断是否为平衡二叉树

 

输入一棵二叉树,1,判断该二叉树是否是平衡二叉树,2,计算深度

 

public class Solution {
	// 注意使用全局变量
	boolean isBalance = true;
	public boolean IsBalanced_Solution(TreeNode root) {
		lengthOfTree(root);
		return isBalance;
	}

	private int lengthOfTree(TreeNode root) {
		if (root == null)
			return 0;
		int left = lengthOfTree(root.left);
		int right = lengthOfTree(root.right);
		if (Math.abs(left - right) > 1)
			isBalance = false;
		return Math.max(left, right) + 1;

	}

	// 每个结点被遍历多次的解法
	public boolean IsBalancedTree(TreeNode root) {
		// 空树为true
		if (root == null)
			return true;
		int leftDepth = TreeDepth(root.left);
		int rightDepth = TreeDepth(root.right);
		if (Math.abs(leftDepth - rightDepth) > 1)
			return false;
		return IsBalancedTree(root.left) 
				&& IsBalancedTree(root.right);
	}

	// 计算树的深度,注意加1
	public int TreeDepth(TreeNode root) {
		if (root == null)
			return 0;
		// 注意最后加1,因为左右子树的深度大的+根节点的深度1
		return Math.max(TreeDepth(root.left), 
				TreeDepth(root.right)) + 1;
	}
}

 

数组中只出现一次的数字

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

使用map

 

// num1,num2分别为长度为1的数组。传出参数
	// 将num1[0],num2[0]设置为返回结果
	// 因为题目要求最多出现两次,可以用list,如果存在就删除,最后list剩下的就是两个数字。
	// 但是要判断list的长度必须大于1,才符合要求、
	public void FindNumsAppearOnce1(int[] array, int num1[], int num2[]) {
		java.util.HashMap map = new java.util.HashMap<>();
		for (int num : array) {
			if (!map.containsKey(num))
				map.put(num, 1);
			else
				map.put(num, map.get(num) + 1);
		}
		int i = 0;
		for (int num : array) {
			if (map.get(num) == 1) {
				if (i == 1) {
					num2[0] = num;
					break;
				} else {
					num1[0] = num;
					i++;
				}
			}

		}
	}

位运算中异或的性质:两个相同数字异或=0,一个数和0异或还是它本身。
当只有一个数出现一次时,我们把数组中所有的数,依次异或运算,最后剩下的就是落单的数,因为成对儿出现的都抵消了。
 依照这个思路,我们来看两个数(我们假设是AB)出现一次的数组。我们首先还是先异或,剩下的数字肯定是A、B异或的结果,这个结果的二进制中的1,表现的是A和B的不同的位。我们就取第一个1所在的位数,假设是第3位,接着把原数组分成两组,分组标准是第3位是否为1。如此,相同的数肯定在一个组,因为相同数字所有位都相同,而不同的数,肯定不在一组。然后把这两个组按照最开始的思路,依次异或,剩余的两个结果就是这两个只出现一次的数字。

 

使用异或

 

public void FindNumsAppearOnce(int[] array, int num1[], int num2[]) {
		if (array == null || array.length < 2)
			return;
		int bitResult = 0;
		for (int i = 0; i < array.length; i++)
			bitResult ^= array[i];
		int index = findFirstBitIs1(bitResult);
		for (int i = 0; i < array.length; i++)
			if (isBit1(array[i], index))
				num1[0] ^= array[i];
			else
				num2[0] ^= array[i];
	}

	// 判断target的右侧index位是否为1
	private boolean isBit1(int target, int index) {
		return ((target >> index) & 1) == 1;
	}

	// 查找num右侧第一个1的下标
	private int findFirstBitIs1(int num) {
		int indexBit = 0;
		// 注意判断位数合法性
		while ((num & 1) == 0 && indexBit < 32) {
			indexBit++;
			num >>= 1;
		}
		return indexBit;
	}

和为S的连续正数序列

小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
输出描述:
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序

 

 

 

import java.util.ArrayList;
/*
*初始化small=1,big=2;
*small到big序列和小于sum,big++;大于sum,small++;
*当small增加到(1+sum)/2是停止
*/
public class Solution {
	public ArrayList> FindContinuousSequence(int sum) {
		ArrayList> ret = new ArrayList<>();
		// 不重复,最少有2个数字
		if (sum < 3)
			return ret;
		// 初始化small=1,big=2;
		int small = 1;
		int big = 2;
		while (small != (sum + 1) / 2) {
			int curSum = sumOfList(small, big);
			if (curSum == sum) {
				// 注意此时list不能使用全局,并clear,因为是引用!只有最后一个结果!!!
				ArrayList list = new ArrayList<>();
				for (int i = small; i <= big; i++)
					list.add(i);
				ret.add(list);
				big++;
			} else if (curSum < sum)
				big++;
			else
				small++;
		}
		return ret;
	}
	// 计算list内数字之和
	private int sumOfList(int small, int big) {
		int sum = 0;
		for (int i = small; i <= big; i++)
			sum += i;
		return sum;
	}
}

 

和为S的两个数字

输入一个递增排序的数组和一个数字S,在数组中查找两个数,是的他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
输出描述:
对应每个测试案例,输出两个数,小的先输出

 

import java.util.ArrayList;
public class Solution {
	// 数列满足递增,设两个头尾两个指针i和j,
	// 若ai + aj == sum,就是答案(和一定,两个数字相差越远乘积越小)
	// 若ai + aj > sum,aj肯定不是答案之一(前面已得出 i 前面的数已是不可能),j -= 1
	// 若ai + aj < sum,ai肯定不是答案之一(前面已得出 j 后面的数已是不可能),i += 1
	// O(n)
	// 已经排好序,运用数学上的夹逼
	public ArrayList FindNumbersWithSum(int[] array, int sum) {
		ArrayList list = new ArrayList<>();
		if (array == null || array.length < 2)
			return list;
		int i = 0;
		int j = array.length - 1;
		while (i < j) {
			int small = array[i];
			int big = array[j];
			if (small + big == sum) {
				list.add(small);
				list.add(big);
				break;
			} else if (small + big > sum)
				j--;
			else
				i++;
		}
		return list;
	}
}

 

左旋转字符串

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

 

public class Solution {
	public String LeftRotateString(String str, int n) {
		if (str == null || str.trim().length() == 0)
			return str;
		int len = str.length();
		n = n % len;
		char[] charStrs = str.toCharArray();
		// 翻转字符串前n个字符
		reverse(charStrs, 0, n - 1);
		// 翻转字符串后面部分
		reverse(charStrs, n, len - 1);
		// 翻转整个字符串
		reverse(charStrs, 0, len - 1);
		return String.valueOf(charStrs);
	}

	private void reverse(char[] charStrs, int i, int j) {
		while (i < j) {
			char temp = charStrs[i];
			charStrs[i] = charStrs[j];
			charStrs[j] = temp;
			i++;
			j--;
		}
	}
}

 

翻转单词顺序列

牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?

 

public class Solution {
	public String ReverseSentence(String str) {
		// 注意trim
		if (str == null || str.trim().length() == 0)
			return str;
		String[] strs = str.split(" ");
		// 借用StringBuffer,从后往前append
		StringBuffer sb = new StringBuffer();
		for (int i = strs.length - 1; i >= 0; i--) {
			sb.append(strs[i]);
			if (i > 0)
				sb.append(" ");
		}
		return sb.toString();
	}
}

 

扑克牌顺子

LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)...他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子.....LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何。为了方便起见,你可以认为大小王是0。

 

public class Solution {
	// 考略到顺子的特性,最大值和最小值之差绝对为4,
		// 然而又有大小王的存在,所以a[4]-a[index] <=4
		// 此题关键是去重和0的个数,还有最大最小的差值
		public boolean isContinuous(int[] numbers) {
			if (numbers == null || numbers.length != 5)
				return false;
			Arrays.sort(numbers);
			// 大小王看成0,最多4个
			// index为0的个数,也是第一个不为0的数字下标
			int index = 0;
			while (numbers[index] == 0)
				index++;
			for (int i = index; i < numbers.length - 1; i++)
				// 判断有没有重复扑克
				if (numbers[i] == numbers[i + 1])
					return false;
			return numbers[4] - numbers[index] <= 4 
					&& index < 5;
		}
}

孩子们的游戏(圆圈中最后剩下的数)

约瑟夫环问题

每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

递推公式

如果只求最后一个报数胜利者的话,我们可以用数学归纳法解决该问题,为了讨论方便,先把问题稍微改变一下,并不影响原意:
问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人 继续从0开始报数。求胜利者的编号。
我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新      的约瑟夫环(以编号为k=m%n的人开始):
k  k+1  k+2  ... n-2, n-1, 0, 1, 2, ... k-2并且从k开始报0。
现在我们把他们的编号做一下转换:
k     --> 0
k+1   --> 1
k+2   --> 2
...
...
k-2   --> n-2
k-1   --> n-1
变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解: 例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情 况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x'=(x+k)%n。
令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]。
递推公式
f[1]=0;
f[i]=(f[i-1]+m)%i;  (i>1)
有了这个公式,我们要做的就是从1-n顺序算出f[i]的数值,最后结果是f[n]。 因为实际生活中编号总是从1开始,我们输出f[n]+1。

import java.util.LinkedList;
public class Solution {
	// 第一种方法使用递推公式
	public int LastRemaining_Solution(int n, int m) {
		if (m < 1 || n < 1)
			return -1;
		int last = 0;
		// i代表有目前有个人
		for (int i = 2; i <= n; i++)
			last = (last + m) % i;
		return last;
	}
	// 第二种方法使用循环链表过程
	public int LastRemaining(int n, int m) {
		// 使用链表模拟,考虑删除节点的效率,使用LinkedList
		LinkedList list = new LinkedList();
		for (int i = 0; i < n; i++)
			list.add(i);
		int bt = 0;
		while (list.size() > 1) {
			// 删除第m个人,即报数为m-1的人(从0开始)
			bt = (bt + m - 1) % list.size();
			list.remove(bt);
		}
		return list.size() == 1 ? list.get(0) : -1;
	}
}

 

求1~n的和(不使用乘除)

求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)(Java只能用递归实现)

或者使用math.pow()函数

 

public class Solution {
	// &&两侧的表达式结果必须为boolean型,
	// 所有&&右侧要用一个无关变量a判断是否与result相等,
	// 让右侧的表达式返回boolean型。不管返回的是true还是false,
	// 我们的目的仅仅是让&&右侧的表达式执行。
	// &&连接的表达式,必须要将最终的boolean结果赋给变量,否则编译报错!
	public int Sum_Solution(int n) {
		int result = 0;
		int temp = 0;
		// 借用&&的短路功能
		boolean flag = (n > 0) && temp == (result += Sum_Solution(n - 1));
		// return ((int) Math.pow(n, 2) + n) >> 1;
		return result;
	}
}

 

不用加减乘除做加法

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
        首先看十进制是如何做的: 5+7=12,三步走
        第一步:相加各位的值,不算进位,得到2。
        第二步:计算进位值,得到10. 如果这一步的进位值为0,那么第一步得到的值就是最终结果。
        第三步:重复上述两步,只是相加的值变成上述两步的得到的结果2和10,得到12。
        同样我们可以用三步走的方式计算二进制值相加: 5-101,7-111 第一步:相加各位的值,不算进位,得到010,二进制每位相加就相当于各位做异或操作,101^111。
        第二步:计算进位值,得到1010,相当于各位做与操作得到101,再向左移一位得到1010,(101&111)<<1。
        第三步重复上述两步, 各位相加 010^1010=1000,进位值为100=(010&1010)<<1。
         继续重复上述两步:1000^100 = 1100,进位值为0,跳出循环,1100为最终结果。

 

public class Solution {
	// 使用递推实现
	public int AddF(int num1, int num2) {
		int sum = 0;
		int carry = 0;
		do {
			sum = num1 ^ num2;
			carry = (num1 & num2) << 1;
			num1 = sum;
			num2 = carry;
			// 注意使用do while
		} while (num2 != 0);
		return sum;
	}

	// 使用递归实现
	public int Add(int num1, int num2) {
		if (num2 == 0)
			return num1;
		return Add(num1 ^ num2, (num1 & num2) << 1);
	}
}

字符串转数字

将一个字符串转化为数字,注意处理非法输入的情况0,可以为空,无法转返回0

 

public class Solution {
	// 方法1简单高效
	public int StrToIntWithExtion(String str) {
		int res = 0;
		try {
			res = Integer.valueOf(str);
		} catch (NumberFormatException e) {
			return 0;
		}
		return res;
	}

	// 方法2
	public int StrToInt(String str) {
		if (str == null || str.length() == 0)
			return 0;
		char[] charArr = str.trim().toCharArray();
		for (int i = 0; i < str.length(); i++) {
			if ((charArr[0] == '+' || charArr[0] == '-')
					// 避免数组长度为1,只有'+''-'情况
					&& charArr.length > 1)
				continue;
			if (!Character.isDigit(charArr[i]))
				return 0;
		}
		return Integer.parseInt(str);
	}
}

 

数组中重复的数字

在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复的数字2或者3。

 

import java.util.ArrayList;
import java.util.Arrays;
public class cho重复的数字 {
	public boolean duplicate2(int numbers[], 
			int length, int[] duplication) {
		// 合法性校验
		if (numbers == null || numbers.length == 0 
				|| length <= 0) {
			duplication[0] = -1;
			return false;
		}
		for (int i = 0; i < length; i++)
			if (numbers[i] < 0 || numbers[i] > length - 1) {
				duplication[0] = -1;
				return false;
			}
		for (int i = 0; i < length; i++) {
			while (numbers[i] != i) {
				if (numbers[i] == numbers[numbers[i]]) {
					duplication[0] = numbers[i];
					return true;
				}
				// 交换两个数字
				int temp = numbers[i];
				numbers[i] = numbers[temp];
				numbers[temp] = temp;
			}
		}
		return false;
	}
	//使用额外空间
	public boolean duplicateWithList(int numbers[], 
			int length, int[] duplication) {
		if (numbers == null || numbers.length == 0) {
			duplication[0] = -1;
			return false;
		}
		ArrayList list = new ArrayList<>();
		for (int i = 0; i < length; i++) {
			if (list.contains(numbers[i])) {
				duplication[0] = numbers[i];
				return true;
			}
			list.add(numbers[i]);
		}
		return false;
	}
	//使用排序
	public boolean duplicateWithSort(int numbers[], 
			int length, int[] duplication) {
		if (numbers == null || numbers.length == 0) {
			duplication[0] = -1;
			return false;
		}
		Arrays.sort(numbers);
		for (int i = 0; i < length - 1; i++)
			if (numbers[i] == numbers[i + 1]) {
				duplication[0] = numbers[i];
				return false;
			}
		return true;
	}
}

 

构建乘积数组

给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。

 

public class Solution {
	// 简单粗暴的一种方式是O(N^2)
	public int[] multiplyWithFor(int[] A) {
		int len = A.length;
		// 定义一个结果对象
		int[] result = new int[len];
		// 定义一个基本的量
		int rst = 1;
		for (int i = 0; i < len; i++) {
			// 如果相同,就路过继续
			for (int j = 0; j < len; j++) {
				if (i == j) {
					continue;
				}
				// 如果不同,就相乘
				rst *= A[j];
			}
			result[i] = rst;
			rst = 1; // 还原基本的量
		}
		return result;
	}
	// 新建一个新数组B, 对A数组i项左侧自上往下累乘,
	// 对A数组i项右侧自下往上累乘 时间复杂度O(n)
	public int[] multiply(int[] A) {
		// 将B拆分为A[0] *...* A[i-1]和A[n-1]*...*A[i+1] 两部分
		if (A == null || A.length == 0)
			return A;
		int len = A.length;
		int[] B = new int[len];
		B[0] = 1;
		// 先计算左下三角形,此时B[0]只有一个元素,舍为1,
		// B[0]不包括A[0]
		for (int i = 1; i < len; i++)
			B[i] = B[i - 1] * A[i - 1];
		// 只需要保留上一个计算结果,不需要数组保存
		int tmp = 1;
		
		/*
		 * for (int i = len - 2; i >= 0; i--) { 
		 *  tmp *= A[i+ 1]; 
		 *  B[i] *= tmp; }
		 */
		// 计算右上三角形
		for (int i = len - 1; i >= 0; i--) {
			// B[i]最终结果是左侧和右侧的乘积
			B[i] *= tmp;
			tmp *= A[i];
		}
		System.out.println(Arrays.toString(B));
		return B;
	}
}

 

正则表达式匹配

 

请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配

 

/*
当模式中的第二个字符不是“*”时:
  1、如果字符串第一个字符和模式中的第一个字符相匹配,
  那么字符串和模式都后移一个字符,然后匹配剩余的。 
  2、如果字符串第一个字符和模式中的第一个字符相不匹配,直接返回false。
  而当模式中的第二个字符是“*”时:
  如果字符串第一个字符跟模式第一个字符不匹配,则模式后移2个字符,继续匹配。
  如果字符串第一个字符跟模式第一个字符匹配,可以有3种匹配方式: 
  1、模式后移2字符,相当于x*被忽略; 
  2、字符串后移1字符,模式后移2字符; 
  3、字符串后移1字符,模式不变,即继续匹配字符下一位,因为*可以匹配多位; 
  这里需要注意的是:Java里,要时刻检验数组是否越界。*/
public class Solution {
	public boolean match(char[] str, char[] pattern) {
		if (str == null || pattern == null) {
			return false;
		}
		int strIndex = 0;
		int patternIndex = 0;
		return matchCore(str, strIndex, pattern, patternIndex);
	}

	public boolean matchCore(char[] str, int strIndex, char[] pattern, int patternIndex) {
		// 有效性检验:str到尾,pattern到尾,匹配成功
		if (strIndex == str.length && patternIndex == pattern.length)
			return true;
		// pattern先到尾,匹配失败
		if (strIndex != str.length && patternIndex == pattern.length)
			return false;
		// 模式第2个是*,且字符串第1个跟模式第1个匹配,分3种匹配模式;如不匹配,模式后移2位
		if (patternIndex + 1 < pattern.length && pattern[patternIndex + 1] == '*') {
			if ((strIndex != str.length && pattern[patternIndex] == str[strIndex])
					|| (pattern[patternIndex] == '.' && strIndex != str.length)) {
				return // 模式后移2,视为x*匹配0个字符
				matchCore(str, strIndex, pattern, patternIndex + 2)
						// 视为模式匹配1个字符
						|| matchCore(str, strIndex + 1, pattern, patternIndex + 2)
						// *匹配1个,再匹配str中的下一个
						|| matchCore(str, strIndex + 1, pattern, patternIndex);

			} else {
				return matchCore(str, strIndex, pattern, patternIndex + 2);
			}
		} // 模式第2个不是*,且字符串第1个跟模式第1个匹配,则都后移1位,否则直接返回false
		if ((strIndex != str.length && pattern[patternIndex] == str[strIndex])
				|| (pattern[patternIndex] == '.' && strIndex != str.length)) {
			return matchCore(str, strIndex + 1, pattern, patternIndex + 1);
		}
		return false;
	}
}

 

字符流中第一个不重复的字符

请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
输出描述:
如果当前字符流没有存在出现一次的字符,返回#字符。

 

import java.util.ArrayList;
import java.util.HashMap;

public class Solution {
	// 使用map记录出现的次数
	HashMap map = new HashMap<>();
	// 使用list记录当前的所有输入的字符,不能使用set等
	ArrayList list = new ArrayList();

	// Insert one char from stringstream
	public void Insert(char ch) {
		if (map.containsKey(ch))
			map.put(ch, map.get(ch) + 1);
		else
			map.put(ch, 1);
		list.add(ch);
	}

	// return the first appearence once char in current stringstream
	public char FirstAppearingOnce() {
		for (Character c : list)
			if (map.get(c) == 1)
				return c;
		return '#';
	}
}

 

链表中环的入口结点

一个链表中包含环,请找出该链表的环的入口结点。

 

//使用set
	public ListNode EntryNodeOfLoopWithSet(ListNode pHead) {
		HashSet set = new HashSet<>();
		// set.add()返回值是boolean
		while (pHead != null) {
			if (!set.add(pHead))
				return pHead;
			pHead = pHead.next;
		}
		return null;
	}

 

/*算法思想:    
  第一步,找环中相汇点。分别用p1,p2指向链表头部,
 * p1每次走一步,p2每次走二步,直到p1==p2找到在环中的相汇点。    
 * 第二步,找环的入口。
 * 接上步,当p1==p2时,p2所经过节点数为2x,p1所经过节点数为x,
 * 设环中有n个节点,p2比p1多走一圈有2x=n+x; n=x;    
 * 可以看出p1实际走了一个环的步数,再让p2指向链表头部,p1位置不变,p1,p2每次走一步直到p1==p2; 
 * 此时p1指向环的入口。    
 * 比剑指offer上更为简洁。*/
public class Solution {
	public ListNode EntryNodeOfLoop(ListNode pHead) {
		if (pHead == null)
			return null;
		ListNode meetNode = meetingNode(pHead);
		if (meetNode == null)
			return null;
		ListNode fast = meetNode;
		ListNode slow = pHead;
		while (slow != fast) {
			slow = slow.next;
			fast = fast.next;
		}
		return slow;
	}
	private ListNode meetingNode(ListNode pHead) {
		ListNode slow = pHead;
		ListNode fast = pHead;
		while (fast != null && fast.next != null) {
			slow = slow.next;
			fast = fast.next.next;
			if (slow == fast)
				return slow;

		}
		return null;
	}
}

删除链表中重复的结点

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,1,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5;2,重复的结点保留一个,如1->4->4->5处理后为1->4->5

重复结点不保留

 

	public ListNode deleteDuplication(ListNode pHead) {
		if (pHead == null)
			return null;
		int myfirst = -1;
		if (myfirst == pHead.val)
			myfirst = -2;
		// 新建一个节点,防止头结点要被删除
		ListNode head = new ListNode(myfirst);
		head.next = pHead;
		ListNode p = head;
		ListNode q = head.next;
		while (q != null) {
			// 此while跳过重复结点
			while (q.next != null && q.val == q.next.val)
				q = q.next;
			// 如果指针移动了
			if (p.next != q) {
				q = q.next;
				// 让p指向非重复结点后的第一个
				p.next = q;
			} else {
				p = q;
				q = q.next;
			}

		}
		return head.next;
	}

重复结点保留

public ListNode deleteDuplicates(ListNode head) {
		if (head == null)
			return null;
		ListNode cur = head;
		while (cur.next != null) {
			if (cur.next.val == cur.val) {
				cur.next = cur.next.next;
			} else {
				cur = cur.next;
			}
		}
		return head;
	}

表示数值的字符串

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。

 

public class Solution {
	public boolean isNumeric(char[] str) {
		if (str == null)
			return false;
		int length = str.length;
		if (length == 0)
			return true;
		int idx = 0;
		if (str[0] == '+' || str[0] == '-')
			idx++;
		int num = 0;
		int dot = 0;
		int e = 0;
		while (idx < length) {
			if (str[idx] >= '0' && str[idx] <= '9') {
				idx++;
				num = 1;
				// .前面可以没有数字
			} else if (str[idx] == '.') {
				// e后面不能有.,e的个数不能大于1
				if (dot > 0 || e > 0)
					return false;

				dot++;
				idx++;
			} else if (str[idx] == 'e' || str[idx] == 'E') {
				// 重复e或者e前面没有数字
				if (e > 0 || num == 0) {
					return false;
				}
				e++;
				idx++;
				// 符号不能在最后一位
				if (idx < length && 
						(str[idx] == '-' || str[idx] == '+')) {
					idx++;
				}
				// 表示e或者符号在最后一位
				if (idx == length) {
					return false;
				}
			} else {
				return false;
			}

		}
		return true;
	}
	// "-.123" 为true
	public boolean isNumericWithMatchs(char[] str) {
		String res = String.valueOf(str);
		return res.matches("[+-]?[0-9]{0,}(\\.?[0-9]{1,})?([Ee][+-]?[0-9]{1,})?");
	}
}

二叉树的下一个结点

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

首先知道中序遍历的规则是:左根右,然后作图

剑指Offer——编程题的Java实现(更新完毕……)_第1张图片

结合图,我们可发现分成两大类:1、有右子树的,那么下个结点就是右子树最左边的点;(eg:D,B,E,A,C,G) 2、没有右子树的,也可以分成两类,a)是父节点左孩子(eg:N,I,L) ,那么父节点就是下一个节点 ; b)是父节点的右孩子(eg:H,J,K,M)找他的父节点的父节点的父节点...直到当前结点是其父节点的左孩子位置。如果没有(eg:M),那么他就是尾节点。

class TreeLinkNode {
	int val;
	TreeLinkNode left = null;
	TreeLinkNode right = null;
	// 父节点
	TreeLinkNode next = null;

	TreeLinkNode(int val) {
		this.val = val;
	}
}
public class Solution {
	public TreeLinkNode GetNext(TreeLinkNode pNode) {
		if (pNode == null)
			return null;
		// 如果有右子树,则找右子树的最左节点
		if (pNode.right != null) {
			// 如果此时pNode没有左子树,那么它就是下一个结点
			pNode = pNode.right;
			while (pNode.left != null)
				pNode = pNode.left;
			return pNode;
		}
		// 非跟结点,并且没有右子树
		while (pNode.next != null) {
			// 找到一个结点是该其父亲的左孩子
			if (pNode.next.left == pNode)
				return pNode.next;
			pNode = pNode.next;
			// 找到返回父节点,此时pNode.next也有可能为空
		}
		// 是跟结点且没有左子树
		return null;
	}
}

对称的二叉树

请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

二叉树是否对称,只要采用前序、中序、后序、层次遍历等任何一种遍历方法,分为先左后右和先右后左两种方法,只要两次结果相等就说明这棵树是一颗对称二叉树。
import java.util.Stack;
public class Solution{
	// 迭代版本
	boolean isSymmetricalWithIter(TreeNode pRoot) {
		Stack s = new Stack<>();
		if (pRoot == null)
			return true;
		s.push(pRoot.left);
		s.push(pRoot.right);
		while (!s.isEmpty()) {
			TreeNode tmp1 = s.pop();
			TreeNode tmp2 = s.pop();
			// 注意if顺序,及其条件
			if ((tmp1 != null && tmp2 == null) || (tmp1 == null && tmp2 != null))
				return false;
			if (tmp1 != null && tmp2 != null) {
				if (tmp1.val != tmp2.val)
					return false;
				s.push(tmp1.left);
				s.push(tmp2.right);
				s.push(tmp1.right);
				s.push(tmp2.left);
			}
		}
		return true;
	}

	// 递归版本
	boolean isSymmetrical(TreeNode pRoot) {
		return isSymmetrical(pRoot, pRoot);
	}

	private boolean isSymmetrical(TreeNode root1, TreeNode root2) {
		if (root1 == null && root2 == null)
			return true;
		if (root1 == null || root2 == null)
			return false;
		// 类似前序遍历
		if (root1.val != root2.val)
			return false;
		// 避免数字重复,要遍历空节点
		return isSymmetrical(root1.left, root2.right) && isSymmetrical(root1.right, root2.left);
	}
}

把二叉树打印成多行

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

 

import java.util.*;
public class Solution{
	ArrayList> Print(TreeNode pRoot) {
		ArrayList> ans = new ArrayList<>();
		if (pRoot == null)
			return ans;
		Queue q = new LinkedList<>();
		q.offer(pRoot);
		// 下一层需要打印的结点数
		int nextLevel = 0;
		// 当前层需要打印的结点数
		ArrayList tmp = new ArrayList<>();
		int toBePrinted = 1;
		while (!q.isEmpty()) {
			TreeNode node = q.poll();
			tmp.add(node.val);
			if (node.left != null) {
				q.offer(node.left);
				nextLevel++;
			}
			if (node.right != null) {
				q.offer(node.right);
				nextLevel++;
			}
			toBePrinted--;
			if (toBePrinted == 0) {
				ans.add(new ArrayList<>(tmp));
				tmp.clear();
				// 下次打印的是q的所有结点
				// or toBePrinted=q.size();省去nextLevel变量
				toBePrinted = nextLevel;
				nextLevel = 0;
			}
		}
		return ans;
	}
}

 

之字形打印二叉树

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。(使用两个栈实现)

 

import java.util.*;
public class Solution{
	public ArrayList> Print(TreeNode pRoot) {
		ArrayList> ans = new ArrayList<>();
		if (pRoot == null)
			return ans;
		Stack stack = new Stack<>();
		Stack nextStack = new Stack<>();
		stack.add(pRoot);
		int flag = 0;
		ArrayList lay = new ArrayList<>();
		while (!stack.isEmpty()) {
			TreeNode node = stack.pop();
			lay.add(node.val);
			// 如果当前是从左到右遍历,按左子树右子树的顺序添加
			if (flag == 0) {
				if (node.left != null)
					nextStack.add(node.left);
				if (node.right != null)
					nextStack.add(node.right);
			} else// 如果当前是从右到左遍历,按右子树左子树的顺序添加
			{
				if (node.right != null)
					nextStack.add(node.right);
				if (node.left != null)
					nextStack.add(node.left);
			}
			if (stack.isEmpty()) {
				// 交换两个栈
				Stack tmp = stack;
				stack = nextStack;
				nextStack = tmp;
				// 标记下一层处理的方向
				flag = 1 - flag;
				ans.add(new ArrayList<>(lay));
				lay.clear();
			}
		}
		return ans;

	}
}

序列化二叉树

请实现两个函数,分别用来序列化和反序列化二叉树

 

public class Solution {
	String Serialize(TreeNode root) {
		StringBuffer sb = new StringBuffer();
		if (root == null) {
			sb.append("#,");
			return sb.toString();
		}
		sb.append(root.val + ",");
		sb.append(Serialize(root.left));
		sb.append(Serialize(root.right));
		return sb.toString();
	}

	int index = -1;
	TreeNode Deserialize(String str) {
		index++;
		int len = str.length();
		if (index >= len)
			return null;
		String[] strr = str.split(",");
		TreeNode node = null;
		if (!strr[index].equals("#")) {
			node = new TreeNode(Integer.valueOf(strr[index]));
			node.left = Deserialize(str);
			node.right = Deserialize(str);
		}
		return node;
	}
}

二叉搜索树的第k个结点

给定一颗二叉搜索树,请找出其中的第k大的结点。例如, 5 / \ 3 7 /\ /\ 2 4 6 8 中,按结点数值大小顺序第三个结点的值为4。

使用中序遍历,的顺序就是二叉搜索时从小到大的排序。

 

import java.util.Stack;
public class Solution{
	// 使用递归实现,java无引用注意使用全局变量
	int index = 0;
	TreeNode KthNode(TreeNode pRoot, int k) {
		if (pRoot != null) {
			TreeNode node = KthNode(pRoot.left, k);
			if (node != null)
				return node;

			if (++index == k)
				return pRoot;

			node = KthNode(pRoot.right, k);
			if (node != null)
				return node;
		}
		return null;
	}

	// 使用栈实现
	TreeNode KthNodeWithStack(TreeNode pRoot, int k) {
		Stack stack = new Stack<>();
		while (pRoot != null || !stack.isEmpty()) {
			if (pRoot != null) {
				stack.push(pRoot);
				pRoot = pRoot.left;
			} else {
				pRoot = stack.pop();
				if (--k == 0)
					return pRoot;
				pRoot = pRoot.right;
			}
		}
		return null;

	}
}

数据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
//Java的PriorityQueue 是从JDK1.5开始提供的新的数据结构接口,默认内部是自然排序,结果为小顶堆,也可以自定义排序器,比如下面反转比较,完成大顶堆。 
//思路:
//为了保证插入新数据和取中位数的时间效率都高效,这里使用大顶堆+小顶堆的容器,并且满足:
//1、两个堆中的数据数目差不能超过1,这样可以使中位数只会出现在两个堆的交接处;
//2、大顶堆的所有数据都小于小顶堆,这样就满足了排序要求。
//用两个堆保存数据,保持两个堆的数据保持平衡(元素个数相差不超过1)大顶堆存放的数据要比小顶堆的数据小当两个推中元素为偶数个,将新加入元素加入到大顶堆,如果要加入的数据,比小顶堆的最小元素大,先将该元素插入小顶堆,然后将小顶堆的最小元素插入到大顶堆。当两个推中元素为奇数个,将新加入元素加入到小顶堆,如果要加入的数据,比大顶堆的最大元素小,先将该元素插入大顶堆,然后将大顶堆的最大元素插入到小顶堆。
public class Solution {
	int count = 0;
	PriorityQueue minheap = new PriorityQueue();
	// 11是默认的初始容量
	PriorityQueue maxheap = new PriorityQueue(11, new Comparator() {
		@Override
		// PriorityQueue默认是小顶堆,实现大顶堆,需要反转默认排序器
		public int compare(Integer o1, Integer o2) {
			return o2.compareTo(o1);
		}
	});

	public void Insert(Integer num) {
		count++;
		if ((count & 1) == 0) {// 判断偶数
			if (!maxheap.isEmpty() && num < maxheap.peek()) {
				maxheap.offer(num);
				num = maxheap.poll();
			}
			minheap.offer(num);
		} else {
			if (!minheap.isEmpty() && num > minheap.peek()) {
				minheap.offer(num);
				num = minheap.poll();
			}
			maxheap.offer(num);
		}
	}

	public Double GetMedian() {
		if (count == 0)
			throw new RuntimeException("非法");
		double median = 0;
		// 总数为奇数时,大顶堆堆顶就是中位数
		if ((count & 1) == 1)
			median = maxheap.peek();
		else
			// 注意2.0
			median = (maxheap.peek() + minheap.peek()) / 2.0;
		return median;

	}
}

滑动窗口的最大值

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

 

import java.util.*;

public class Solution {
	public static void main(String[] args) {
		int num[] = { 4, 2, 3, 2, 6, 2, 5, 1 };
		System.out.println(maxInWindows(num, 4));
	}
	// 利用一个双向队列deque dq保存最大值。
	// 窗口右滑遇到新数时,首先判断若左侧滑出窗口的是最大值,则将双向队列的对手元素删除。然后依次将新数与队列末尾元素进行比较,依次删掉队尾小于新数的元素直到遇到大的元素或队列为空。将新数压入队列,保证队列中保存的元素是降序存在的,这里队列保存的就是当下滑动窗口的第一大元素、第二大元素···
	// 另外,此处队列保存的的是原数组的下标,这样既可以保存数值,也可以判断队列中的数是否滑出窗口。
	public static ArrayList maxInWindows(int[] num, int size) {
		ArrayList maxWindows = new ArrayList<>();
		if (num == null || size == 0 || num.length == 0 || num.length < size)
			return maxWindows;
		Deque dq = new LinkedList<>();
		for (int i = 0; i < size; i++) {
			while (!dq.isEmpty() && num[i] > num[dq.getLast()])
				dq.removeLast();
			dq.addLast(i);
		}
		//System.out.println(dq);
		for (int i = size; i < num.length; i++) {
			maxWindows.add(num[dq.getFirst()]);
			while (!dq.isEmpty() && num[i] >= num[dq.getLast()])
				dq.removeLast();

			if (!dq.isEmpty() && dq.getFirst() <= i - size)
				dq.removeFirst();
			dq.addLast(i);
			System.out.println(i + "--" + dq);
		}
		maxWindows.add(num[dq.getFirst()]);
		return maxWindows;
	}
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(数据结构与算法)