剑指offer题目(持续更新)
单例模式
单线程
/**
* 线程不安全
*/
class Singleton1 {
private static Singleton1 instance = null;
// 构造方法私有化,防止外部使用new创建对象
private Singleton1() {
System.out.println("单例模式1");
}
// 获取实例的方法
public static Singleton1 getInstance() {
if (instance == null) {
instance = new Singleton1();
}
return instance;
}
}
多线程
有人提出在getInstance()方法上同步锁,但是锁住一整个方法可能粒度过大,不利于效率。既然锁方法不太好,那么锁代码呢?
class Singleton2 {
private static Singleton2 instance = null;
// 构造方法私有化,防止外部使用new创建对象
private Singleton2() {
System.out.println("单例模式2");
}
// 获取实例的方法
public static Singleton2 getInstance() {
if (instance == null) {
synchronized (Singleton2.class) {
instance = new Singleton2();
}
}
return instance;
}
}
这样做看似解决了线程安全问题,其实不然。设现有线程A和B,在t1时刻线程A和B均已通过判空语句但都未取得锁资源;t2时刻时,A先取得锁资源进入临界区(被锁的代码块),执行new操作创建实例对象,然后退出临界区,释放锁资源。t3时刻,B取得被A释放的锁资源进入临界区,执行new操作创建实例对象,然后退出临界区,释放锁资源。明显地,Singleton被实例化两次。
改进:
/**
* 线程安全,但是如果instance已经存在不能直接返回,也会被锁住,效率很低下,还可以优化
*/
class Singleton3 {
private static Singleton3 instance = null;
private Singleton3() {
System.out.println("单例模式3");
}
public static Singleton3 getInstance() {
synchronized (Singleton3.class) {
if (instance == null) {
instance = new Singleton3();
}
}
return instance;
}
}
双检查锁机制(推荐)
class Singleton4 {
//注意此处加上了volatile关键字
private static Singleton4 instance = null;
private Singleton4() {
System.out.println("单例模式4");
}
public static Singleton4 getInstance() {
if (instance == null) {
synchronized (Singleton4.class) {
if (instance == null) {
instance = new Singleton4();
}
}
}
return instance;
}
}
在JDK1.5以前,DCL(双检查锁机制)是不稳定的,有时也可能创建多个实例,在1.5以后开始提供volatile关键字修饰变量来达到稳定效果。
多线程测试入口
public class Test {
public static void main(String[] args) {
//创建实现了Runnable接口的匿名类
Runnable run = () -> Singleton2.getInstance();
for (int i = 0; i < 50; i++) {
Thread thread = new Thread(run);
thread.start();
}
}
}
总结
单例是为了保证系统中只有一个实例,其关键点有:
- 私有构造函数
- 声明静态单例对象
- 构造单例对象之前要加锁(lock一个静态的object对象)
- 需要两次检测单例实例是否已经被构造,分别在锁之前和锁之后
1.为何要检测两次?
如上面所述,有可能延迟加载或者缓存原因,造成构造多个实例,违反了单例的初衷。
2.构造函数能否公有化?
不行,单例类的构造函数必须私有化,单例类不能被实例化,单例实例只能静态调用
3.lock住的对象为什么要是object对象,可以是int吗?
不行,锁住的必须是个引用类型。如果锁值类型,每个不同的线程在声明的时候值类型变量的地址都不一样,那么上个线程锁住的东西下个线程进来会认为根本没锁,相当于每次都锁了不同的门,没有任何卵用。而引用类型的变量地址是相同的,每个线程进来判断锁多想是否被锁的时候都是判断同一个地址,相当于是锁在通一扇门,起到了锁的作用。
数组
二维有序数组中查找
思路:用target与数组右上角的值进行比较,小于右上角,则跳过该列,大于右上角,则跳过该行
public class Test {
public static void main(String[] args) {
int[][] arr = {
{1, 2, 8, 9},
{2, 4, 9, 12},
{4, 7, 10, 13},
{6, 8, 11, 15},
};
int[][] arr2 = {
{1, 2, 8, 9},
};
System.out.println(find(arr, 11));
}
public static boolean find(int[][] arr, int target) {
if (arr.length < 1) return false;
int i = 0, j = arr[0].length - 1;
// 用target与数组右上角的值进行比较
while (j > 0 && i < arr.length - 1) {
// target小于右上角,则跳过该列
while (target < arr[i][j] && j > 0) {
j--;
}
// target大于右上角,则跳过该行
while (target > arr[i][j] && i < arr.length - 1) {
i++;
}
if (target == arr[i][j]) {
System.out.println("i:" + (i + 1) + ",j:" + (j + 1));
return true;
}
}
return false;
}
}
旋转数组的最小数字
主要考虑三种情况:
- 正常情况:{7,8,1,2,3,4,5,6}
- 有序情况:{1, 2, 3, 4, 5, 6}
- 相等情况:{1, 1, 0, 1, 1, 1, 1, 1}
public class Test {
public static void main(String[] args) {
// int[] a = {5,6,7,8,1,2,3,4};
// int[] a = {1, 1, 0, 1, 1, 1, 1, 1};
int[] a = {1, 2, 3, 4, 5, 6};
System.out.println(findMin(a));
}
/**
* 使用二分法进行查找
*/
public static int findMin(int[] arr) {
if (arr == null || arr.length == 0) {
return -1;
}
int left = 0;
int right = arr.length - 1;
int mid = left;//初始值原因见下方
/**
* 正常情况,左边会大于等于右边,否则就已经是有序的数组,第一个就是最小值,因此mid初始为left
*/
while (arr[left] >= arr[right]) {
if (right - left == 1) {
mid = right;
break;
}
mid = (left + right) / 2;
// 左边=中间=右边,只能遍历,{1, 1, 0, 1, 1, 1, 1, 1};
if (arr[left] == arr[right] && arr[right] == arr[mid]) {
int min = Integer.MAX_VALUE;
for (int i = left; i <= right; i++) {
if (min > arr[i]) {
min = arr[i];
}
}
return min;
}
// 左边<=中间,说明最小值在[mid,right]
if (arr[left] <= arr[mid]) {
left = mid;
}
// 右边>=中间,说明最小值在[left,mid]
else if (arr[right] >= arr[mid]) {
right = mid;
}
}
return arr[mid];
}
调整数组 使奇数在偶数前面
并保证奇数和奇数,偶数和偶数之间的相对位置不变。
基本解法
//基本解法
public static void sort(int[] numbers) {
if (numbers == null || numbers.length == 0) {
return;
}
int left = 0, right = numbers.length - 1;
while (left <= right) {
while ((numbers[left] & 0x1) == 1) left++;
while ((numbers[right] & 0x1) == 0) right--;
if (left <= right) {
int tmp = numbers[left];
numbers[left] = numbers[right];
numbers[right] = tmp;
}
}
}
可扩展的实现,解耦,完成各种条件
// 可扩展的实现
public class Test {
public static void main(String[] args) {
int[] arr = {2, 2, 1, 3, 6, 9, 8, 10, 7, 4, 5};
sort(arr, new Condition() {
@Override
public boolean check(int a) {
if ((a & 0x1) == 1)
return false;
else {
return true;
}
}
});
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
public static void sort(int[] numbers, Condition c) {
if (numbers == null || numbers.length == 0) {
return;
}
int left = 0, right = numbers.length - 1;
while (left <= right) {
while (left <= right && c.check(numbers[left])) left++;
while (left <= right && !c.check(numbers[right])) right--;
if (left <= right) {
int tmp = numbers[left];
numbers[left] = numbers[right];
numbers[right] = tmp;
}
}
}
private interface Condition {
public boolean check(int a);
}
}
保证顺序
import java.util.*;
public class Solution {
//O(n^2)
public void reOrderArray(int [] array) {
if (array == null || array.length == 0) {
return;
}
for (int i = 0; i < array.length; i++) {
for (int j = array.length - 1; j > i; j--) {
if (array[j] % 2 == 1 && array[j - 1] % 2 == 0) {
int temp = array[j];
array[j] = array[j - 1];
array[j - 1] = temp;
}
}
}
}
// O(n),辅助空间
public void reOrderArray2(int [] array) {
if (array == null || array.length == 0) {
return;
}
int left = 0;
int right = array.length - 1;
int[] copy = new int[array.length];
int i = 0;
int j = array.length - 1;
//从前向后扫描奇数
while (left < array.length ) {
if(array[left] % 2 == 1)
copy[i++] = array[left];
left++;
}
//从后向前扫描偶数
while (right >= 0 ) {
if(array[right] % 2 == 0)
copy[j--] = array[right];
right--;
}
for(int k=0;k<array.length;k++){
array[k]=copy[k];
}
}
}
顺时针打印矩阵
public class Test {
public static void main(String[] args) {
int[][] matrix = {
{1, 2, 3, 4},
{12, 13, 14, 5},
{11, 16, 15, 6},
// {10,9,8,7},
};
ArrayList<Integer> list = printMatrix(matrix);
for (int i : list) {
System.out.print(i + " ");
}
}
public static ArrayList<Integer> printMatrix(int [][] matrix) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return null;
}
int start = 0;// 因为每次打印都是从对角线[0,0]、[1,1]……的开始,因此只需要一个变量来标记
int row = matrix.length;
int col = matrix[0].length;
ArrayList<Integer> printList = new ArrayList<>();
while (start * 2 < row && start * 2 < col) {
ArrayList<Integer> result = print(matrix, row, col, start);
printList.addAll(result);
++start;
}
return printList;
}
public static ArrayList<Integer> print(int[][] matrix, int row, int col, int start) {
int endX = col - start - 1;
int endY = row - start - 1;
ArrayList<Integer> list = new ArrayList<>();
// 打印从左到右的上面一行 :第一行肯定要打印的
for (int i = start; i <= endX; i++) {
list.add(matrix[start][i]);
}
// 打印从上到下的右面的一列:第二行能不能打印的条件在循环中了
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; i--) {
list.add(matrix[i][start]);
}
}
return list;
}
}
数组中超过一半的数字(还有一种解法)
解法1:基于partition的函数的O(n)算法,因为数字超过半数,因此将原数组排序后,中位数一定就是那个数值(如果输入是)
解法2:一个数字出现次数超过数组长度的一半,说明他出现的次数比其他所有数字出现的次数还多。因此可以用result,time来分别保存统计的数字,以及出现的次数。遍历时如果相同time+1,不同time-1,当time=0时,result更新为当前数字,time重置为1
public static int MoreThanHalfNum_Solution(int[] array) {
//1.输入合法性验证
if (array == null || array.length == 0) return 0;
//2.统计超过半数的数字
int result = array[0];
int time = 1;
for (int i = 1; i < array.length; i++) {
//当time=0时,result更新为当前数字,time重置为1
if (time == 0) {
result = array[i];
time = 1;
continue;
}
//遍历时如果相同time+1,不同time-1
if (result == array[i])
time++;
else
time--;
}
//3.是否超过半数验证
time = 0;
for (int i = 0; i < array.length; i++) {
if (result == array[i]) {
time++;
}
}
if (time * 2 <= array.length)
result = 0;
return result;
}
最小的k个数(包含海量数据)
解法1,小数据量:快排方法 O(n)
public class Test {
public static void main(String[] args) {
int[] array = {2, 3, 0, 2, 5, 7, 8, 9, 1};
ArrayList<Integer> re = GetLeastNumbers_Solution(array, 10);
System.out.println(re);
}
public static ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
ArrayList<Integer> result = new ArrayList<>();
//输入检验
if (input == null || input.length == 0 || k <= 0 || k > input.length) return result;
int index = parttition(input, 0, input.length - 1);
int start = 0;
int end = input.length - 1;
while (index != k - 1) {
if (index < k - 1) {
start = index + 1;
} else {
end = index - 1;
}
index = parttition(input, start, end);
}
for (int i = 0; i < k; i++) {
result.add(input[i]);
}
return result;
}
public static int parttition(int[] input, int low, int high) {
int i = low;
int j = high;
int mid = (low + high) / 2;
swap(input, mid, low);
int pivot = input[low];
while (i < j) {
while (input[j] >= pivot && i < j) j--;
while (input[i] <= pivot && i < j) i++;
swap(input, i, j);
}
swap(input, i, low);
return i;
}
public static void swap(int[] input, int i, int j) {
int temp = input[i];
input[i] = input[j];
input[j] = temp;
}
}
解法2,海量数据:使用红黑树 O(nlogk),类似下面介绍的方法5
常见解法思路:
- 最容易想到的方法是将数据全部排序,然后在排序后的集合中进行查找,最快的排序算法的时间复杂度一般为O(nlogn),如快速排序。但是在32位的机器上,每个float类型占4个字节,1亿个浮点数就要占用400MB的存储空间,对于一些可用内存小于400M的计算机而言,很显然是不能一次将全部数据读入内存进行排序的。其实即使内存能够满足要求,该方法也并不高效,因为题目的目的是寻找出最大的10000个数即可,而排序却是将所有的元素都排序了,做了很多的无用功。
- 第二种方法为局部淘汰法,该方法与排序方法类似,用一个容器保存前10000个数,然后将剩余的所有数字与容器内的最小数字相比,如果某一后续元素比容器内最小数字大,则删掉容器内最小元素,并将该元素插入容器,最后遍历完这1亿个数,得到的结果容器中保存的数即为最终结果了。此时的时间复杂度为O(n+m^2),其中m为容器的大小,即10000。
- 第三种方法是分治法,将1亿个数据分成100份,每份100万个数据,找到每份数据中最大的10000个,最后在剩下的100*10000个数据里面找出最大的10000个。如果100万数据选择足够理想,那么可以过滤掉1亿数据里面99%的数据。100万个数据里面查找最大的10000个数据的方法如下:用快速排序的方法,将数据分为2堆,如果大的那堆个数N大于10000个,继续对大堆快速排序一次分成2堆,如果大的那堆个数N大于10000个,继续对大堆快速排序一次分成2堆,如果大堆个数N小于10000个,就在小的那堆里面快速排序一次,找第10000-n大的数字;递归以上过程,就可以找到第1w大的数。参考上面的找出第1w大数字,就可以类似的方法找到前10000大数字了。此种方法需要每次的内存空间为10^6*4=4MB,一共需要101次这样的比较。
- 第四种方法是Hash法。如果这1亿个书里面有很多重复的数,先通过Hash法,把这1亿个数字去重复,这样如果重复率很高的话,会减少很大的内存用量,从而缩小运算空间,然后通过分治法或最小堆法查找最大的10000个数。
- 第五种方法采用最小堆。首先读入前10000个数来创建大小为10000的最小堆,建堆的时间复杂度为O(mlogm)(m为数组的大小即为10000),然后遍历后续的数字,并于堆顶(最小)数字进行比较。如果比最小的数小,则继续读取后续数字;如果比堆顶数字大,则替换堆顶元素并重新调整堆为最小堆。整个过程直至1亿个数全部遍历完为止。然后按照中序遍历的方式输出当前堆中的所有10000个数字。该算法的时间复杂度为O(n*mlogm),空间复杂度是10000(常数)。
public class Test {
public static void main(String[] args) {
int[] array = {2, 3, 0, 2, 5, 7, 8, 9, 1};
ArrayList<Integer> re = GetLeastNumbers_Solution(array, 10);
System.out.println(re);
}
public static ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
ArrayList<Integer> result = new ArrayList<>();
//输入检验
if (input == null || input.length == 0 || k <= 0 || k > input.length) return result;
TreeSet<Integer> topk=new TreeSet();
for (int val : input) {
if (topk.size() < k) {
topk.add(val);
} else {
if(topk.last()>val){
topk.pollLast();
topk.add(val);
}
}
}
result.addAll(topk);
return result;
}
}
访问次数最多的IP
参考:http://yueyemaitian.iteye.com/blog/1180299
算法思想:分而治之+Hash
1、IP地址最多有2^32=4G种取值情况,所以不能完全加载到内存中处理;
2、可以考虑采用分而治之的思想,按照IP地址的Hash(IP) % 1024值,把海量IP日志分别存储到1024个小文件中,这样,每个小文件最多包含4MB个IP地址;
这里解释一下为什么用Hash(IP) % 1024值,如果不用,而直接分类的话,可能会出现这样一种情况,就是有个IP在每个小文件中都存在,而且这个IP并不一定在那个小文件中是数量最多的,那么最终可能选择的结果会有问题,所以这里用了Hash(IP)%1024值,这样的话,通过计算IP的Hash值,相同IP肯定会放到一个文件中,当然了不同的IP的Hash值也可能相同,就存在一个小文件中。
3、对于每一个小文件,可以构建一个IP为key,出现的次数为value的Hash Map,同时记录当前出现次数最多的那个IP地址;
4、可以得到1024个小文件中的出现次数最多的那个IP,再依据常规的排序算法得出总体上出现次数最多的IP。
连续子数组的最大和
解法1 O(n),思路如下:
public class Test {
public static void main(String[] args) {
int[] array = {1,-2,3,10,-4,7,2,-5};
System.out.println(FindGreatestSumOfSubArray(array));
}
public static int FindGreatestSumOfSubArray(int[] array) {
if (array == null || array.length == 0) return 0;
int start = 0;
int end = 0;
int max = array[0];
int sum = array[0]; // 初始化为数组中的第一个数
for (int i = 1; i < array.length; i++) {
// 如果之前的和<=0,说明从之前某个位置开始的子数组的和 会小于 从当前位置开始的子数组的和
// 因此可以不考虑之前的子数组,Sum重置为当前位置的值
if (sum <= 0) {
start = i;
end = i;
sum = array[i];
}else{
sum = sum + array[i];
}
// 更新最大和
if (sum > max) {
max = sum;
end = i; //更新子数组最后的下标
}
}
System.out.println("["+start+","+end+"]");
return max;
}
}
解法2:动态规划
public static int FindGreatestSumOfSubArray(int[] array) {
if (array == null || array.length == 0) return 0;
int len = array.length;
int[] f = new int[len];
f[0] = array[0];
for (int i = 1; i < len; i++) {
// 递归公式
// f[i] = array[i] # i=0 或者 f[i-1]<=0
// f(i) = f[i - 1] + array[i] # f[i-1]>0
f[i] = f[i - 1] <= 0 ? array[i] : f[i - 1] + array[i];
}
int max = Integer.MIN_VALUE;
for (int i : f) {
if (max < i) {
max = i;
}
}
return max;
}
解法3:分治(未实现)
把数组排成最小的数
import java.util.*;
public class Solution {
public String PrintMinNumber(int [] numbers) {
// 输入合法性检测
if (numbers == null || numbers.length == 0) {
return "";
}
// 将数字转为字符串类型
ArrayList<String> nums = new ArrayList<>();
for (int i : numbers) {
nums.add(Integer.toString(i));
}
// 使用自定义的规则 进行排序
Collections.sort(nums, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
//为了避免长度问题,将其拼接后再进行字符串比较
return (s1 + s2).compareTo(s2 + s1);
}
});
// 处理最后的结果
return String.join("",nums);
}
}
数组中的逆序对
public static int InversePairs(int[] array) {
if (array == null || array.length <= 0) {
return 0;
}
return countInverse(array, 0, array.length - 1) % 1000000007;
}
public static int countInverse(int[] array, int low, int high) {
if (low >= high) {
return 0;
}
int mid = (low + high) / 2;
int leftCount = countInverse(array, low, mid);
int rightCount = countInverse(array, mid + 1, high);
// 拷贝一份数组的左右部分,用于排序并统计,注意copyOfRange[begin,end),不包括end位置
int[] leftCopy = Arrays.copyOfRange(array, low, mid + 1);
int[] rightCopy = Arrays.copyOfRange(array, mid + 1, high + 1);
// 排序并统计逆序对
int i = leftCopy.length - 1;
int j = rightCopy.length - 1;
int index = high;
int count = 0;
while (i >= 0 && j >= 0) {
if (leftCopy[i] <= rightCopy[j]) {
array[index--] = rightCopy[j--];
} else {
array[index--] = leftCopy[i--];
count += (j + 1);
// 避免数据溢出,进行取余
if (count > 1000000007) count %= 1000000007;
}
}
// 将剩余的部分拷贝回原数组
for (; i >= 0; i--) {
array[index--] = leftCopy[i];
}
for (; j >= 0; j--) {
array[index--] = rightCopy[j];
}
// 避免数据溢出,进行取余
int result = leftCount + rightCount + count;
if (result > 1000000007) result %= 1000000007;
return result;
}
数字在排序数组中出现的次数
public static int GetNumberOfK(int[] array, int k) {
int count = 0;
if (array != null || array.length > 0) {
// 先找到第一个位置
int start = getFirstK(array, k, 0, array.length - 1);
if (start == -1) return 0;// 查找不到在则直接返回
// 再找最后出现的位置
int end = getLastK(array, k, 0, array.length - 1);
count = end - start + 1;
}
return count;
}
public static int getFirstK(int[] array, int k, int low, int high) {
if (low > high) {
return -1;
}
int mid = (low + high) / 2;
int midData = array[mid];
if (midData == k) {
// 如果已经是数组第一个 或者 mid前一个位置小于k,则说明已经找到第一个k的位置了
if (mid == 0 || (mid > 0 && array[mid - 1] < k)) {
return mid;
} else {
high = high - 1;
}
} else if (midData < k) {
low = mid + 1;
} else {
high = mid - 1;
}
return getFirstK(array, k, low, high);
}
public static int getLastK(int[] array, int k, int low, int high) {
if (low > high) {
return -1;
}
int mid = (low + high) / 2;
int midData = array[mid];
if (midData == k) {
// 如果已经是数组最后一个 或者 mid后一个位置大于k,则说明已经找到最后一个k的位置了
if (mid == high || (mid < high && array[mid + 1] > k)) {
return mid;
} else {
low = mid + 1;
}
} else if (midData < k) {
low = mid + 1;
} else {
high = mid - 1;
}
return getLastK(array, k, low, high);
}
数组中只出现一次的数字
public class Test {
public static void main(String[] args) {
int[] array = {2, 4, 3, 6, 3, 2, 5, 5};
int[] num1 = new int[1];
int[] num2 = new int[1];
FindNumsAppearOnce(array, num1, num2);
System.out.println(num1[0]);
System.out.println(num2[0]);
}
public static void FindNumsAppearOnce(int[] array, int num1[], int num2[]) {
if (array == null || array.length < 2) return;
// 先遍历一遍求出异或的结果
int xorResult = 0;
for (int i : array) {
xorResult ^= i;
}
int index = findFirstBit(xorResult); //找到第一个bit为1的下标
// 利用第一个bit为1的下标将数组分割为两部分,每部分index位置的bit相同
int n1 = 0;
int n2 = 0;
for (int i : array) {
if (isSameBit(i, index))
n1 ^= i;
else
n2 ^= i;
}
num1[0] = n1;
num2[0] = n2;
}
// 从右往左寻找第一个为1的二进制下标
public static int findFirstBit(int num) {
int i = 1;
int index = 0;
while (index < Integer.SIZE) {
if ((i & num) == i) break;
i = i << 1;
index++;
}
return index;
}
// 判断指定下标的位置的二进制是否为1
public static boolean isSameBit(int num, int leftShift) {
int i = 1 << leftShift;
return (num & i) == i;
}
}
相似题:leetcode:single-number、 single-number-ii
在数组中寻找 和为s的两个数字
思路:分别从最左和最右逼近,如果和小于s,则i++,如果和大于s,则j–
// 如果有多对数字的和等于S,输出两个数的乘积最小的。
public static ArrayList<Integer> FindNumbersWithSum(int[] array, int sum) {
ArrayList<Integer> result = new ArrayList<Integer>();
if (array == null || array.length < 2) return result;
int i = 0;
int j = array.length - 1;
while (i < j) {
if (array[i] + array[j] == sum) {
result.add(array[i]);
result.add(array[j]);
return result;
} else if (array[i] + array[j] < sum) {
i++;
} else {
j--;
}
}
return result;
}
寻找和为s的连续序列
public static ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
if (sum <= 0) return result;
// 初始化将sum分为一大一小的2个数字
int small = sum / 2;
int big = sum / 2 + 1;
int curSum = small + big;
while (small >= 1) {
if (curSum == sum) {
ArrayList<Integer> list = new ArrayList<Integer>();
for (int i = small; i <= big; i++) {
list.add(i);
}
result.add(list);
// 通过添加更小的small,获得产生新的序列
small--;
curSum += small;
} else if (curSum > sum) {
// 偏大,则剪去big,加上更小的small
small--;
curSum = curSum - big + small;
big--;
} else {
// 偏小则再加一个更小的small
small--;
curSum += small;
}
}
// 序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序
result.sort(Comparator.comparingInt(c -> c.get(0)));
return result;
}
数组中重复的数字
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
思路:
public class Solution {
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
// Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
// 这里要特别注意~返回任意重复的一个,赋值duplication[0]
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
public boolean duplicate(int numbers[],int length,int [] duplication) {
if (numbers == null || numbers.length <= 1) return false;
for (int i = 0; i < length; i++) {
while (numbers[i] != i) {
int m = numbers[i];
// 如果m和第m个数字相等,则说明m该去的位置被人占了,就找到了第一个重复的数字
if (m == numbers[m]) {
duplication[0] = m;
return true;
}
// 否则交换m到对应的第m个位置
numbers[i] = numbers[m];
numbers[m] = m;
}
}
return false;
}
}
构建乘积数组
先算左边部分A0–Ai,再乘以右边部分Ai-1—An-1
package offer;
/**
* 构建乘积数组
*
* @author jizx
* @date 2018/07/24 17:27
*/
public class Multiply {
public int[] multiply(int[] A) {
if (A == null || A.length == 0) return null;
int len = A.length;
int[] B = new int[len];
//自底向上地累乘A[i-1]
B[0] = 1;
for (int i = 1; i < len; i++) {
B[i] = B[i - 1] * A[i - 1];
}
//自上向下地在原来基础上,再累成A[i]
int temp = 1;//这就是右半部分的乘积
for (int i = len - 1; i >= 0; i--) {
B[i] = B[i] * temp;
temp = temp * A[i];
}
return B;
}
}
有序数组的交集
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 有序数组的交集
*
* @author jizx
* @date 2018/08/19 10:18
*/
public class Jiaoji {
public static void main(String[] args) {
int[] a = {1, 3, 8, 10, 11};
int[] b = {1, 3, 9, 10, 12};
ArrayList<Integer> result = new ArrayList<>();
jiaoji(a, b, result);
System.out.println(result);
}
public static void jiaoji(int[] a, int[] b, List<Integer> result) {
if (a == null || b == null || a.length == 0 || b.length == 0) return;
int[] min = a;
int[] max = b;
if (a.length > b.length) {
min = b;
max = a;
}
int index = Arrays.binarySearch(max, min[min.length / 2]);
if (index >= 0) {
result.add(max[index]);
// min中去除min[min.length / 2],max中去除max[index]因为以及找到了
jiaoji(Arrays.copyOfRange(min, 0, min.length / 2), Arrays.copyOfRange(max, 0, index), result);
jiaoji(Arrays.copyOfRange(min, min.length / 2 + 1, min.length), Arrays.copyOfRange(max, index + 1, max
.length), result);
} else {
index = -index - 1;
jiaoji(Arrays.copyOfRange(min, 0, min.length / 2), Arrays.copyOfRange(max, 0, index), result);
jiaoji(Arrays.copyOfRange(min, min.length / 2 + 1, min.length), Arrays.copyOfRange(max, index, max.length),
result);
}
}
}
字符串
4.字符串替换空格(未实现)
- 先遍历一次字符串,这样就能统计出字符串中空格的总数
- 准备两个指针p1,p2,。p1指向原始字符串的末尾,p2指向替换后的字符串的末尾
- p1、p2同时移动,先前复制,直到p1遇到空格
- p2向前插入‘%20’
- p1、p2继续同时先前移动,直到p1与p2相遇
35.第一个只出现一次的字符
import java.util.*;
public class Solution {
public int FirstNotRepeatingChar(String str) {
// 非法输入检测
if (str == null || "".equals(str)) return -1;
char[] chars = str.toCharArray();
HashMap<Character, Integer> counter = new HashMap<>();
// 第一遍遍历进行统计
for (char c : chars) {
int num = counter.get(c) != null ? counter.get(c) + 1 : 1;
counter.put(c, num);
}
// 第二遍遍历进行搜索
int index = 0;
for (char c : chars) {
if (counter.get(c) == 1) return index;
index++;
}
return -1;
}
}
翻转单词顺序
public class Test {
public static void main(String[] args) {
System.out.println(ReverseSentence("student. a am I"));
}
public static String ReverseSentence(String str) {
if (str == null || str.length() <= 0) return "";
char[] chars = str.toCharArray();
// 先将整个字符串翻转
reverseChars(chars, 0, chars.length);
// 通过空格来定位每个单词的位置,然后进行单词的翻转
int start = 0;//指向单词第一个字符
int end = 0;//指向单词后的第一个空格
for (char c : chars) {
if (c != ' ') {
end++;
if (end == chars.length)
reverseChars(chars, start, end);
} else {
// start与end之间是一个单词的情况
if (start != end) {
reverseChars(chars, start, end);
}
end++;
start = end;// start移动到end位置
}
}
return new String(chars);
}
// start:翻转的开始位置,不包括end位置的字符
public static void reverseChars(char[] str, int start, int end) {
int i = start;
int j = end - 1;
while (i < j) {
char c = str[i];
str[i] = str[j];
str[j] = c;
i++;
j--;
}
}
}
左旋转字符串(循环左移字符串)
题目描述(推荐):汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。
public class Test {
public static void main(String[] args) {
System.out.println(LeftRotateString("abcXYZdef",3));
}
public String LeftRotateString(String str,int n) {
if (str == null || str.length() <= 0) return "";
char[] chars = str.toCharArray();
// 通过3次翻转即可实现
reverseChars(chars,0,n);
reverseChars(chars,n,chars.length);
reverseChars(chars,0,chars.length);
return new String(chars);
}
// 字符翻转
public static void reverseChars(char[] str, int start, int end) {
int i = start;
int j = end - 1;
while (i < j) {
char c = str[i];
str[i] = str[j];
str[j] = c;
i++;
j--;
}
}
}
将字符串转换为整数
题目描述:
将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0
// 测试用例有:+123,-123,234k234
public static int StrToInt(String str) {
if (str == null || str.length() <= 0) return 0;
int result = 0;
boolean isMinus = false;
char[] chars = str.toCharArray();
for (int i = 0; i < chars.length; i++) {
// 如果不是数字,则判断是否是在第一位的正负号
if (chars[i] < '0' || chars[i] > '9') {
if ((i != 0) || (chars[i] != '+' && chars[i] != '-')) return 0;
else {
if (chars[i] == '-') isMinus = true;
continue;
}
}
result = result * 10 + chars[i] - '0';// 注意字符要与‘0’进行相减,才是真正对应的数字
}
return isMinus ? -1 * result : result;
}
字符串判断是否为有效数值
package offer;
/**
* 判断字符串是否为有效的数值
*
* @author jizx
* @date 2018/07/24 17:45
*/
public class IsNumeric {
public static void main(String[] args) {
IsNumeric app=new IsNumeric();
System.out.println(app.isNumeric("-1.e0".toCharArray()));
}
public boolean isNumeric(char[] str) {
if (str == null || str.length == 0) return false;
boolean dot = false;
boolean e = false;
boolean sign = false;
for (int i = 0; i < str.length; i++) {
if ((str[i] == '+' || str[i] == '-')) {
if (i == 0) sign = true;// 首位
else if (str[i - 1] == 'e' || str[i - 1] == 'E') {} // 紧跟在e后面
else return false;
} else if (str[i] == 'e' || str[i] == 'E') {
if (e == true) return false;// 已经有e了
else if (i == str.length - 1) return false; // e后面没有数字了
else e = true;
} else if (str[i] == '.') {
// 在e后面,不能有小数点、或者已经有小数点了
if (e == true || dot == true) return false;
else dot = true;
} else {
if (str[i] < '0' || str[i] > '9') return false; // 其他非法字符
}
}
return true;
}
}
字符串的排列
import java.util.ArrayList;
import java.util.*;
public class Solution {
ArrayList<String> result = new ArrayList<String>();
public ArrayList<String> Permutation(String str) {
if (str == null || str.length() == 0) return result;
char[] chars = str.toCharArray();
permutation(0, chars);
//
HashSet<String> set=new HashSet<>(result);
result.clear();
result.addAll(set);
Collections.sort(result);
return result;
}
public void permutation(int prefix, char[] str) {
if (prefix == str.length) {
result.add(new String(str));
return;
}
// [a,b,c],prefix表示固定的位置,比如a,然后将a与后面的字符依次进行交换
for (int i = prefix; i < str.length; i++) {
// 将固定位置的值与数组中的第i个字符交换
char temp = str[i];
str[i] = str[prefix];
str[prefix] = temp;
// 递归处理[b,c]
permutation(prefix + 1, str);
//还原交换
temp = str[i];
str[i] = str[prefix];
str[prefix] = temp;
}
}
}
链表
java实现链表
/**
* @author jizx
* @date 2018/04/20 15:09
*/
public class Test {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList();
list.add("a");
list.add("c");
list.add("bb");
list.insert(3, "1");
list.printList();
// System.out.println(list.find("2"));
System.out.println(list.search(3));
list.printList();
}
}
class LinkedList<E> {
private class Node<E> {
private E data;
Node next = null;
public Node() {
data = null;
}
public Node(E data) {
this.data = data;
}
}
private Node head;
private Node tail;
private int length;
public LinkedList() {
head = new Node();
tail = head;
length = 0;
}
public void add(E data) {
Node newNode = new Node(data);
tail.next = newNode;
tail = newNode;
length++;
}
public void insert(int position, E data) {
if (position >= 0 && position <= length) {
int i = 0;
Node current = head;
while (i < position) {
current = current.next;
i++;
}
Node temp = current.next;
Node newNode = new Node(data);
newNode.next = temp;
current.next = newNode;
} else {
System.out.println("超出范围");
}
length++;
}
public void remove(E data) {
Node pre = head;
Node current = head.next;
while (current != null) {
if (current.data.equals(data)) {
pre.next = current.next;
break;
} else {
pre = current;
current = current.next;
}
}
}
public void set(int position, E data) {
Node current = head.next;
while (current != null) {
if (current.data.equals(data)) {
break;
} else {
current = current.next;
}
}
}
public int find(E data) {
E result = null;
Node current = head.next;
int position = 0;
while (current != null) {
if (current.data.equals(data)) {
return position;
} else {
current = current.next;
position++;
}
}
return -1;
}
public E search(int position) {
int i = -1;
Node current = head;
if (position >= 0 && position < length) {
while (i < position) {
i++;
current = current.next;
}
return (E) current.data;
} else {
System.out.println("超出范围");
return null;
}
}
public int length() {
return length;
}
public void printList() {
Node current = head.next;
while (current != null) {
System.out.println(current.data);
current = current.next;
}
}
}
从尾到头打印链表
O(1)时间删除链表结点(未实现)
链表中倒数第k个节点
public class Test {
public static void main(String[] args) {
ListNode head = new ListNode(1);
ListNode cur = head;
for (int i = 2; i <= 5; i++) {
ListNode node = new ListNode(i);
cur.next = node;
cur = node;
}
try {
ListNode newHead = findKNode(head, 0);
while (newHead != null) {
System.out.println(newHead.val);
newHead = newHead.next;
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static ListNode findKNode(ListNode head, int k) {
if (head == null|| k <= 0) {
return null;
}
ListNode ahead = head;
ListNode behind = ahead;
// 两者相差k-1个位置,因此ahead先走k-1个位置
for (int i = 0; i < k - 1; i++) {
if (ahead.next != null)
ahead = ahead.next;
else
return null;
}
// 然后两者一起移动
while (ahead.next != null) {
ahead = ahead.next;
behind = behind.next;
}
return behind;
}
}
class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
反转链表
public class Test {
public static void main(String[] args) {
ListNode head = new ListNode(0);
ListNode cur=head;
for (int i = 1; i <= 10; i++) {
ListNode node = new ListNode(i);
cur.next = node;
cur = node;
}
try {
ListNode newHead = ReverseList(head);
while (newHead != null) {
System.out.println(newHead.val);
newHead = newHead.next;
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static ListNode ReverseList(ListNode head) {
// 边界处理
if (head == null) return null;
if (head.next == null) return head;
ListNode remainHead = null; // h:剩余未反转的链表头
ListNode handlingNode = head; // i:正在处理的结点,从head开始,它的next指向原方向,因此正要反转
ListNode reversedHead = null; // j:已经反转的链表头
while (handlingNode != null) {
// 原来 reversedHead handlingNode --> remainHead -->k
// 反转 reversedHead <-- handlingNode remainHead -->k
remainHead = handlingNode.next;
handlingNode.next = reversedHead;
// 更新结点为下一次要处理的位置
reversedHead = handlingNode;
handlingNode = remainHead;
}
return reversedHead;
}
}
class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
合并两个有序的链表
public class Test {
public static void main(String[] args) {
ListNode list1 = new ListNode(1);
ListNode list2 = new ListNode(2);
ListNode cur1 = list1;
ListNode cur2 = list2;
for (int i = 3; i <= 10; i++) {
ListNode node1 = new ListNode(i);
ListNode node2 = new ListNode(++i);
cur1.next = node1;
cur2.next = node2;
cur1 = node1;
cur2 = node2;
}
ListNode newHead = Merge(list1, list2);
while (newHead != null) {
System.out.print(newHead.val + " ");
newHead = newHead.next;
}
}
public static ListNode Merge(ListNode list1, ListNode list2) {
// 有一个链表为空,直接返回另一个链表
if (list1 == null) return list2;
if (list2 == null) return list1;
// 新链表的头结点初始化为较小的链表的头结点
ListNode head = list1.val < list2.val ? list1 : list2;
ListNode temp;
while (list1 != null && list2 != null) {
if (list1.val < list2.val) {
temp = list1.next;
list1.next = list2;
list1 = temp;
} else {
temp = list2.next;
list2.next = list1;
list2 = temp;
}
}
return head;
}
}
class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
复杂链表的复制
/*
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;
}
copyChain(pHead);
copySibling(pHead);
return splitChain(pHead);
}
/**第一步:复制链表,暂时不考虑random 字段
* A->B->C 变为 A->A'->B->B'->C->C'
* */
public void copyChain(RandomListNode pHead) {
RandomListNode cur = pHead;
while (cur != null) {
RandomListNode n = new RandomListNode(cur.label);
n.next = cur.next;
cur.next = n;
cur = n.next;
}
}
/**第二步:考虑复制sibling字段*/
public void copySibling(RandomListNode pHead) {
RandomListNode cur = pHead;
while (cur != null) {
RandomListNode clone = cur.next;
RandomListNode sibling = cur.random;
if (sibling != null) {
clone.random = sibling.next;
}
cur = clone.next;
}
}
/**第三步:拆分链表,抽取出copy链表*/
public RandomListNode splitChain(RandomListNode pHead) {
RandomListNode copy = pHead.next;
RandomListNode cur1 = pHead;
RandomListNode cur2 = pHead.next;
while (cur1 != null && cur2 != null) {
cur1.next = cur2.next;
cur1 = cur1.next;
if(cur1!=null){
cur2.next=cur1.next;
}
cur2=cur2.next;
}
return copy;
}
}
查找链表的中间节点(未实现)
思路:采用快慢指针的方式查找单链表的中间节点,快指针一次走两步,慢指针一次走一步,当快指针走完时,慢指针刚好到达中间节点
两个链表的第一个公共结点
import java.util.*;
public class Test {
public static void main(String[] args) {
ListNode a1=new ListNode(1);
ListNode a2=new ListNode(2);
ListNode a3=new ListNode(3);
ListNode a4=new ListNode(4);
ListNode a5=new ListNode(5);
ListNode a6=new ListNode(6);
ListNode a7=new ListNode(7);
a1.next=a2;
a2.next=a3;
a3.next=a6;
a4.next=a5;
a5.next=a6;
a6.next=a7;
System.out.println(FindFirstCommonNode(a1,a4));
}
public static ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null || pHead2 == null) return null;
// 先遍历一遍找出两个链表的长度
int len1 = getLength(pHead1);
int len2 = getLength(pHead2);
ListNode longList = pHead1;
ListNode shortList = pHead2;
int diff = len1 - len2;
if (len1 < len2) {
longList = pHead2;
shortList = pHead1;
diff = len2 - len1;
}
// 然后让长的链表先走多的长度
for (int i = 0; i < diff; i++) {
longList = longList.next;
}
// 最后一起走,第一个相同结点就是公共结点
while (longList != null && shortList != null) {
if (longList == shortList){
return longList;
}
longList = longList.next;
shortList = shortList.next;
}
return null;
}
public static int getLength(ListNode list) {
int len = 0;
ListNode current = list;
while (current != null) {
current = current.next;
len++;
}
return len;
}
}
链表中环的入口节点
如果一个链表中包含环,如何找出环的入口节点?
import java.util.*;
/**
* 链表中环的入口
*
* @author jizx
* @date 2018/07/03 18:39
*/
public class EntryNodeOfLoop {
public static void main(String[] args) {
EntryNodeOfLoop app = new EntryNodeOfLoop();
ListNode a1=new ListNode(1);
ListNode a2=new ListNode(2);
ListNode a3=new ListNode(3);
ListNode a4=new ListNode(4);
ListNode a5=new ListNode(5);
ListNode a6=new ListNode(6);
a1.next=a2;
a2.next=a3;
a3.next=a4;
a4.next=a5;
a5.next=a6;
a6.next=a3;
System.out.println(app.EntryNodeOfLoop(null));
}
public ListNode EntryNodeOfLoop(ListNode pHead) {
// 1.判断是否有环,如果有则返回环内的一个节点
ListNode loopNode = meetingNode(pHead);
if (loopNode == null) return null;
// 2.计数环的长度
int count = 1;
ListNode search = loopNode.next;
while (loopNode != search) {
count++;
search = search.next;
}
// 3.找到入口
ListNode behind = pHead;// 慢指针
ListNode prev = pHead;//快指针
// 让快指针先走count个节点,这样才能保证他们相遇的节点是入口节点
while (count-- > 0) {
prev = prev.next;
}
while(prev!=behind){
prev=prev.next;
behind=behind.next;
}
return prev;
}
/**
* 使用快慢指针,当其相遇时有环,快指针==null时无环
*/
private ListNode meetingNode(ListNode head) {
if (head == null) return null;
ListNode slow = head;// 慢指针
ListNode fast = slow.next;//快指针
while (slow != null && fast != null) {
// 如果快慢指针相遇、快指针的下一个是慢指针,则有环
if (slow == fast || fast.next == slow) {
return slow;
}
// 更新快慢指针位置
slow = slow.next;
fast = fast.next;
if (fast != null) fast = fast.next;
}
return null;
}
}
删除链表中重复的结点
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
/**
* 删除链表中重复的结点
*
* @author jizx
* @date 2018/07/06 10:41
*/
public class DeleteDuplication {
public static void main(String[] args) {
DeleteDuplication app = new DeleteDuplication();
int[] nums = {1, 1, 2, 3, 3, 4, 5, 5};
ListNode a1 = app.create(nums);
a1 = app.deleteDuplication(a1);
while (a1 != null) {
System.out.print(a1.val + "-->");
a1 = a1.next;
}
}
public ListNode deleteDuplication(ListNode pHead) {
// null或者只有一个结点时 直接返回
if (pHead == null || pHead.next == null) return pHead;
ListNode newHead = new ListNode(0);// 新链表的头结点
newHead.next = pHead;
ListNode pre = newHead;// 记录前一个不重复的结点
ListNode cur = pre.next;// 当前结点
while (cur != null && cur.next != null) {
// 当前结点与下一结点相等
if (cur.val == cur.next.val) {
do{
cur = cur.next;
}
while (cur.next != null && cur.next.val == cur.val);
cur = cur.next; // 更新到这个暂时不重复的点
pre.next = cur; // pre的next连接到这个暂时不重复的点
}
// 当前结点与下一结点不相等
else {
pre = cur;// 则pre更新到这个可以确认是不重复的点上
cur = cur.next; // 移动cur
}
}
return newHead.next;
}
// 保留一个重复结点
public ListNode deleteDuplication2(ListNode pHead) {
ListNode p = pHead;
while (p != null) {
ListNode q = p;
while (q.next != null) {
if (p.val == q.next.val) {
q.next = q.next.next;
} else
q = q.next;
}
p = p.next;
}
return pHead;
}
public ListNode create(int[] nums) {
ListNode cur = new ListNode(0);
ListNode head = cur;
for (int i = 0; i < nums.length; i++) {
cur.next = new ListNode(nums[i]);
cur = cur.next;
}
return head.next;
}
}
树
重建二叉树
public class Solution {
public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
if (pre.length == 0 || in.length == 0) {
return null;
}
//先取【先序】的第一个元素,作为根结点
TreeNode root = new TreeNode(pre[0]);
//然后遍历【中序】,找到对应的根结点,进行划分数组
for (int i = 0; i < in.length; i++) {
if (in[i] == pre[0]) {
//注意i对应的是中序的下标,因此是截取pre[1,i+1]
root.left = reConstructBinaryTree(Arrays.copyOfRange(pre, 1, i + 1), Arrays.copyOfRange(in, 0, i));
root.right = reConstructBinaryTree(Arrays.copyOfRange(pre, i + 1, pre.length), Arrays.copyOfRange(in, i + 1,
in.length));
}
}
return root;
}
}
层序遍历
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> result=new ArrayList<>();
if(root==null){
return result;
}
LinkedList<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
TreeNode node=queue.poll();
result.add(node.val);
if(node.left!=null) queue.offer(node.left);
if(node.right!=null) queue.offer(node.right);
}
return result;
}
树的子结构
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 isTree = false;
if (root1 != null && root2 != null) {
// 如果当前两个根结点相等,则进行检查
if (root1.val == root2.val) isTree = isSubTree(root1, root2);
// 否则递归对root1的左子树判断
if (!isTree) isTree = HasSubtree(root1.left, root2);
// 左子树也没有,则递归对root1的右子树判断
if (!isTree) isTree = HasSubtree(root1.right, root2);
}
return isTree;
}
public boolean isSubTree(TreeNode root1, TreeNode root2) {
if(root2==null) return true; //root2匹配完了,因此true
if(root1==null) return false; //root2不为null而root1==null,则不相同
if(root1.val!=root2.val) return false;
//当前结点相等,则判断左子树,然后判断右子树
return isSubTree(root1.left, root2.left) && isSubTree(root1.right, root2.right);
}
}
二叉树的镜像
class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
public class Solution {
// 先序遍历的方法
public static void Mirror(TreeNode root) {
if (root == null) {
return;
}
// 交换子节点
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
// 递归处理子节点
Mirror(root.left);
Mirror(root.right);
}
}
二叉搜索树的后序遍历序列
public class Test {
public static void main(String[] args) {
int[] a = {5, 7, 6, 9, 11, 10, 8};
int[] b = {6, 8, 7, 5};
int[] c = {4, 8, 6, 12, 16, 14, 10};
int[] d = {};
System.out.println(VerifySquenceOfBST(d));
}
public static boolean VerifySquenceOfBST(int[] sequence) {
// 为空则false
if (sequence == null || sequence.length == 0) {
return false;
}
// 只有一个点,则为true,直接返回不用递归了
if (sequence.length == 1) {
return true;
}
int i = 0;
int high = sequence.length - 1;
int root = sequence[high];
// 以root判断大小,寻找划分点,i最终会落在第一个大于root的位置
while (sequence[i] < root) {
i++;
}
//判断右子树是否都大于root,否则就不可能是后序遍历
for (int j = i; j < high; j++) {
if (root > sequence[j])
return false;
}
// copyOfRange : [begin,end),不包含end
int[] leftTree = Arrays.copyOfRange(sequence, 0, i);
int[] rightTree = Arrays.copyOfRange(sequence, i, high);
boolean leftResult = true;
boolean rightResult = true;
if (leftTree.length > 0) {
leftResult = VerifySquenceOfBST(leftTree);
}
if (rightTree.length > 0) {
rightResult = VerifySquenceOfBST(rightTree);
}
return leftResult && rightResult;
}
}
二叉树中和为某一值的路径
public static ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
ArrayList<ArrayList<Integer>> allRoad = new ArrayList<ArrayList<Integer>>();
if (root != null) {
Stack<Integer> road = new Stack<>();
path(root, target, road, 0, allRoad);
}
return allRoad;
}
public static void path(TreeNode root, int target, Stack<Integer> road, int currentSum, ArrayList<ArrayList<Integer>> allRoad) {
currentSum = root.val + currentSum;
TreeNode left = root.left;
TreeNode right = root.right;
// 超过目标值,不必向下继续
if (currentSum > target) {
return;
}
// 叶结点,但值不等于目标值,返回
if (left == null && right == null && currentSum != target) {
return;
}
road.push(root.val);
// 叶结点,值等于目标值,添加路径
if (left == null && right == null && currentSum == target) {
ArrayList<Integer> result = new ArrayList<Integer>();
for (int i : road) {
result.add(i);
}
allRoad.add(result);
}
if (left != null) {
path(left, target, road, currentSum, allRoad);
}
if (right != null) {
path(right, target, road, currentSum, allRoad);
}
road.pop();
}
二叉搜索树与双向链表转换
public class Test {
public static void main(String[] args) {
TreeNode n1 = new TreeNode(1);
TreeNode n2 = new TreeNode(2);
TreeNode n3 = new TreeNode(3);
TreeNode n4 = new TreeNode(4);
TreeNode n5 = new TreeNode(5);
TreeNode n6 = new TreeNode(6);
TreeNode n7 = new TreeNode(7);
//构建树
n4.left = n2;
n4.right = n6;
n2.left = n1;
n2.right = n3;
n6.left = n5;
n6.right = n7;
Convert(n4);
}
public static TreeNode Convert(TreeNode pRootOfTree) {
TreeNode lastVisitNode = null;
// 注意,不可以将lastVisitNode直接传入,因为就像传入null,不会赋值给lastVisitNode
lastVisitNode = convertNode(pRootOfTree, lastVisitNode);
// 完成转换,从lastVisitNode往前遍历,到头结点
TreeNode first = lastVisitNode;
while (first != null) {
if(first.left==null) break;
first = first.left;
}
return first;
}
/**
* currentNode:当前节点
* lastVisitNode:最后访问的节点,同时也是双向链表的最右节点
*/
public static TreeNode convertNode(TreeNode currentNode, TreeNode lastVisitNode) {
if (currentNode == null) return null;
//先处理左子树,同时获得左子树最右边的点(最大点)
if (currentNode.left != null) {
lastVisitNode = convertNode(currentNode.left, lastVisitNode);
}
//互相连接,当前节点的left连接lastVisitNode,同时lastVisitNode的right连接当前节点
currentNode.left = lastVisitNode;
if (lastVisitNode != null) lastVisitNode.right = currentNode;
lastVisitNode = currentNode; //最后访问的节点更新为当前节点
// 处理右子树
if (currentNode.right != null) {
lastVisitNode = convertNode(currentNode.right, lastVisitNode);
}
return lastVisitNode;
}
}
class TreeNode {
int val = 0;
TreeNode left = null; // 连接小的结点
TreeNode right = null; // 连接大的结点
public TreeNode(int val) {
this.val = val;
}
}
二叉树的深度,以及是否是平衡二叉树
public class Test {
public static void main(String[] args) {
}
public static int TreeDepth(TreeNode root) {
if (root == null) return 0;
int leftDepth = TreeDepth(root.left);
int rightDepth = TreeDepth(root.right);
return Math.max(leftDepth, rightDepth) + 1;
}
}
class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
public class Test {
public static void main(String[] args) {
TreeNode a1 = new TreeNode(1);
TreeNode a2 = new TreeNode(2);
TreeNode a3 = new TreeNode(3);
TreeNode a4 = new TreeNode(4);
TreeNode a5 = new TreeNode(5);
TreeNode a6 = new TreeNode(6);
TreeNode a7 = new TreeNode(7);
TreeNode a8 = new TreeNode(8);
a1.left=a2;
a1.right=a3;
a2.left=a4;
a2.right=a5;
a5.right=a7;
a3.left=a8;
a3.right=a6;
System.out.println(IsBalanced_Solution(a1));
}
public static boolean IsBalanced_Solution(TreeNode root) {
if (root == null) return true;
return isBalanced(root) != -1;
}
// 返回值 -1:非平衡树,0:null结点,正数:树的深度
public static int isBalanced(TreeNode root) {
if (root == null) return 0;
int leftDepth = isBalanced(root.left);
int rightDepth = isBalanced(root.right);
// 如果左右子树都是平衡树,则进行深度比较;如果深度差满足要求,则返回最大深度
if (leftDepth != -1 && rightDepth != -1) {
if (Math.abs(leftDepth - rightDepth) <= 1) {
return Math.max(leftDepth, rightDepth) + 1;
}
}
return -1;
}
}
class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
中序遍历二叉树,求某一节点的下一中序节点
public static TreeLinkNode GetNext(TreeLinkNode pNode) {
if (pNode == null) return null;
// 1.有右结点,则找该右结点的最左结点
if (pNode.right != null) {
pNode = pNode.right;
while (pNode.left != null) {
pNode = pNode.left;
}
return pNode;
}
/* 2.没有右节点,则向上找父节点
a a
b b
c c
d d
c是当前节点。确定c是在某个节点(a)的左子树中,还是(a)的右子树中;
如果在左子树中,则找到的第一个包含c的节点就是下一个中序输出的节点;
否则,就没有下一个节点了
* */
TreeLinkNode parent = pNode.next;
while (parent != null) {
if (parent.left == pNode) return parent;
pNode = parent;
parent = parent.next;
}
return null;
}
二叉搜索树的第k个结点
给定一颗二叉搜索树,请找出其中的第k小的结点。例如, 5 / \ 3 7 /\ /\ 2 4 6 8 中,按结点数值大小顺序第三个结点的值为4。
序列化二叉树
import java.util.*;
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
// 序列化
String Serialize(TreeNode root) {
if (root == null) return "";
String node = Integer.toString(root.val);
node += ",";//父节点后添加逗号
if (root.left == null)
node += "#";
else
node += Serialize(root.left);
node += ",";//左节点后添加逗号
if (root.right == null)
node += "#";
else
node += Serialize(root.right);
//右节点后 不添加逗号
return node;
}
// 反序列化
TreeNode Deserialize(String str) {
if (str == null || str.length() <= 0) return null;
String[] nodes = str.split(",");
return Deserialize2(nodes);
}
// 正在处理的结点下标
int index=0;
TreeNode Deserialize2(String[] nodes) {
String node = nodes[index++];
if ("#".equals(node)) return null;
TreeNode root = new TreeNode(Integer.parseInt(node));
root.left = Deserialize2(nodes);
root.right = Deserialize2(nodes);
return root;
}
}
队列与栈
两个栈实现队列
import java.util.Stack;
public class Solution {
Stack<Integer> inStack = new Stack<Integer>();// 入队保存的栈
Stack<Integer> outStack = new Stack<Integer>(); // 出队保存的栈
private int size = 0;
public void push(int node) {
inStack.push(node);
size++;
}
public int pop() {
if (size == 0) return -1;
size--;
if (outStack.isEmpty()) {
while (!inStack.isEmpty()) {
outStack.push(inStack.pop());
}
}
return outStack.pop();
}
}
用两个队列实现栈(未实现)
包含min函数的栈
class Solution {
Stack<Integer> stack = new Stack<>();
Stack<Integer> minStack = new Stack<>();
public void push(int node) {
stack.push(node);
if (minStack.empty()) {
minStack.push(node);
return;
}
if (node < minStack.peek()) {
minStack.push(node);
} else {
minStack.push(minStack.peek());
}
}
public void pop() {
if (!stack.empty()) {
stack.pop();
minStack.pop();
}
}
public int top() {
return stack.peek();
}
public int min() {
return minStack.peek();
}
}
栈的压入、弹出序列
public class Test {
public static void main(String[] args) {
int [] a={1,2,3,4,5};
int [] b={4,5,3,1,2};
System.out.println(IsPopOrder(a,b));
}
public static boolean IsPopOrder(int[] pushA, int[] popA) {
Stack<Integer> stack = new Stack<>();
int i = 0, j = 0, lena = pushA.length, lenb = popA.length;
boolean isPopOrder = true;
while (i < lena) {
stack.push(pushA[i]);
i++;
// 栈顶与popA[j]相同,则出栈,否则继续进栈
while (stack.peek() == popA[j] && j < lenb) {
stack.pop();
j++;
}
}
// 栈不为空,或者popA没有遍历完,则不是
if (!stack.isEmpty() || j != lenb) {
isPopOrder = false;
}
return isPopOrder;
}
}
排序
员工年龄排序
public class Test {
public static void main(String[] args) {
int[] a = {23,26,27,28,25,30,54,23,23};
sortAge(a);
for (int i = 0; i < a.length; i++) {
System.out.println(a[i]);
}
}
public static int[] sortAge(int[] arr) {
if (arr == null || arr.length == 0) {
return null;
}
int oldest = 100;
// 创建0-100的数组来统计每个年龄有多少人
int[] ages = new int[oldest + 1];
// 遍历数组进行统计
for (int i = 0; i < arr.length; i++) {
int age = arr[i];
ages[age]++;
}
int index = 0;
// 遍历统计的数组,将年龄存回原数组
for (int age = 0; age <= oldest; age++) {
for (int i = 0; i < ages[age]; i++) {
arr[index] = age;
index++;
}
}
return arr;
}
}
回溯法
矩阵中的路径
/**
* 判断矩阵中是否有路径
*
* @author jizx
* @date 2018/07/09 20:18
*/
public class HasPath {
public static void main(String[] args) {
HasPath app = new HasPath();
char[] matrix = "ABCESFCSADEE".toCharArray();
char[] str = "ABCB".toCharArray();
System.out.println(app.hasPath(matrix, 3, 4, str));
}
public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
if (matrix == null || rows < 1 || cols < 1 || str == null) return false;
boolean[][] visited = new boolean[rows][cols];
// 遍历矩阵中每个位置,穷举
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (findStr(matrix, rows, cols, i, j, str, visited))
return true;
}
}
return false;
}
private int strIndex = 0;
private boolean findStr(char[] matrix, int rows, int cols, int row, int col, char[] str, boolean[][] visited) {
if (strIndex == str.length) return true;
boolean find = false;
// 矩阵中matrix[row][col] 的字符 与 str[strIndex]的相同,则继续探索该位置的四周与str[strIndex++]是否一样
if (row >= 0 && row < rows && col >= 0 && col < cols && !visited[row][col] && matrix[row * cols + col] == str[strIndex]) {
strIndex++;
visited[row][col] = true;
find = findStr(matrix, rows, cols, row - 1, col, str, visited) ||
findStr(matrix, rows, cols, row + 1, col, str, visited) ||
findStr(matrix, rows, cols, row, col - 1, str, visited) ||
findStr(matrix, rows, cols, row, col + 1, str, visited);
// 如果该点的四周都没有找到符合下一字符的,则退回,重置该位置的状态,以及字符匹配的位置
if (!find) {
visited[row][col] = false;
strIndex--;
}
}
return find;
}
}
机器人的运动范围
/**
* 机器人的运动范围
*
* @author jizx
* @date 2018/07/09 21:45
*/
public class RobotMovingCount {
public static void main(String[] args) {
RobotMovingCount app = new RobotMovingCount();
System.out.println(app.movingCount(18, 3, 4));
}
public int movingCount(int threshold, int rows, int cols) {
if (rows < 0 || cols < 0 || threshold < 0) return 0;
boolean[][] visited = new boolean[rows][cols];
return movingCount(threshold, rows, cols, 0, 0, visited);
}
public int movingCount(int threshold, int rows, int cols, int row, int col, boolean[][] visited) {
int count = 0;
if (canIn(rows, cols, row, col, threshold, visited)) {
visited[row][col] = true;
// 能够到达的位置:当前位置+其他四个方向能够到达的总和
count = 1 + movingCount(threshold, rows, cols, row - 1, col, visited) +
movingCount(threshold, rows, cols, row + 1, col, visited) +
movingCount(threshold, rows, cols, row, col - 1, visited) +
movingCount(threshold, rows, cols, row, col + 1, visited);
}
return count;
}
// 判断是否能进入该位置
public boolean canIn(int rows, int cols, int row, int col, int threshold, boolean[][] visited) {
if (!visited[row][col] && row >= 0 && row < rows && col >= 0 && col < cols) {
int sum = 0;
while (row != 0) {
sum += row % 10;
row /= 10;
}
while (col != 0) {
sum += col % 10;
col /= 10;
}
return sum <= threshold;
}
return false;
}
}
递归
斐波那契数数列
public class Solution {
// 非递归
public int Fibonacci(int n) {
int[] initial = {0, 1}; //初始化第0项、第1项
if (n < 2) return initial[n];
// fn=f(n-1)+f(n-2)
int fn_1 = 1;
int fn_2 = 0;
int fn = 0;
for (int i = 2; i <= n; i++) {
fn = fn_1 + fn_2;
fn_2 = fn_1;
fn_1 = fn;
}
return fn;
}
}
青蛙跳台阶
但是要注意与斐波那契数列的下标起始位置不太一样 ,第0 项是1,即:1 1 2 3 5 。。。
public static int JumpFloor(int target) {
int[] initial = {0, 1};
if (target < 2) return initial[target];
int jumpn_1 = 1; //第1项是1
int jumpn_2 = 1; //第0项是1
int num = 0;
for (int i = 2; i <= target; i++) {
num = jumpn_1 + jumpn_2;
jumpn_2 = jumpn_1;
jumpn_1 = num;
}
return num;
}
矩阵覆盖问题
二进制
10.二进制中1的个数
用1扫描法:
public static int numOf1(int n) {
int count = 0;
int matcher = 1;
while (matcher != 0) {
// n与matcher按位 与运算,如果matcer的1 对应n的1,就不会为0
// n:0000101
// m:0000100
if ((n & matcher) != 0) count++;
//让matcer左移,进行扫描
matcher = matcher << 1;
}
return count;
}
减1法
public static int numOf1(int n) {
int count = 0;
while (n != 0) {
// 把一个整数减去1,再和原整数做 与运算,就会把该整数最右边一个1变成0
n = (n - 1) & n;
count++;
}
return count;
}
数值
精度问题
由于精度原因,不能用等号判断两个double 小数是否相等。
当小数点后位数 大于15位时,jvm就会忽略这个精度,这种情况,我们在开发时如果需要更精确的比较double类型,就要用到 BigDecimal 这个类了。
// jdk1.8
double x1= 4.000000000000002;
double x2= 4.000000000000005;
x1<x2 true
BigDecimal x3=new BigDecimal(4.0000000000000002);
BigDecimal x4=new BigDecimal(4.0000000000000005);
x3<x4 true
000000000000002 // double(15位)
0000000000000002 // BigDecimal(16位)
数值的整数次方pow(x,n)
思路:分治
简洁版
public double pow(double x, int n) {
// 底数为0,不可以直接用等号判断,因为有精度的问题
if (Double.compare(x, 0.0) == 0) return 0.0;
//指数<0的情况,取绝对值,最后进行求倒数即可
if (n < 0) return 1.0 / powWithUnsign(x, -n);
else return powWithUnsign(x, n);
}
public double powWithUnsign(double x, int n) {
if (n == 0) return 1.0;
if (n == 1) return x;
double result = powWithUnsign(x, n/2);
if ((n&1) == 1)
return result * result * x;
else
return result * result;
}
详细版
public static double Power(double basse, int exponent) throws Exception {
// 输入底数为0,指数小于0
if (Double.compare(basse, 0.0) == 0 && exponent < 0) {
throw new Exception("非法");
}
int absExponent = exponent;//指数取绝对值
if (exponent < 0) {
absExponent = exponent * -1;
}
double result = PowerWithUnsignedExponent(basse, absExponent);
if (exponent < 0) {
result = 1.0 / result;
}
return result;
}
public static double PowerWithUnsignedExponent(double base, int exponent) {
// 判断人为输入为0的情况,并不是右移产生的
if (exponent == 0) {
return 1;
}
if (exponent == 1) {
return base;
}
double result = PowerWithUnsignedExponent(base, exponent >> 1);
result *= result;
if ((exponent & 0x1) == 1) {
result *= base;
}
return result;
}
打印1到最大的n位数
public static void printNMax(int n) {
if (n <= 0) {
return;
}
char[] number = new char[n];
for (int i = 0; i < number.length; i++) {
number[i] = '0';
}
int a = number[1];
while (!increase(number, 8)) {
printNum(number);
}
}
public static boolean increase(char[] number, int add) {
int sum = 0;
int takeOver = 0;// 进位
boolean isMax = false;
// 从最后一位开始加一
for (int i = number.length - 1; i >= 0; i--) {
sum = number[i] - '0' + takeOver;
// 个位进行加一
if (i == number.length - 1) {
sum += add;
}
// 和大于10,则有进位,否则就可以结束循环
if (sum >= 10) {
// 如果是最高位,则结束
if (i == 0) isMax = true;
else {
sum -= 10;
takeOver = 1;
number[i] = (char) ('0' + sum);
}
} else {
number[i] = (char) ('0' + sum);
break;
}
}
return isMax;
}
public static void printNum(char[] number) {
int i = 0;
boolean isStart = false;
while (i < number.length) {
if (number[i] == '0' && !isStart) {
} else {
isStart = true;
System.out.print(number[i]);
}
i++;
}
System.out.println();
}
34.丑数
public static int GetUglyNumber_Solution(int index) {
if (index <= 0) {
return 0;
}
// 保存丑数的数组,并初始化第一个丑数
int[] uglyNum = new int[index];
uglyNum[0] = 1;
// 指向对应乘积刚好大于当前找到的丑数的下标
int base2 = 0;// 乘以2后的值,刚好略大于当前丑数的下标
int base3 = 0;// 乘以3后的值,刚好略大于当前丑数的下标
int base5 = 0;// 乘以5后的值,刚好略大于当前丑数的下标
int currentIndex = 1;
while (currentIndex < index) {
int nextNum = Math.min(Math.min(uglyNum[base2] * 2, uglyNum[base3] * 3), uglyNum[base5] * 5);
uglyNum[currentIndex] = nextNum;
// 更新下标,使得下标在刚好略大于当前丑数的下标
while (uglyNum[base2] * 2 <= nextNum) base2++;
while (uglyNum[base3] * 3 <= nextNum) base3++;
while (uglyNum[base5] * 5 <= nextNum) base5++;
currentIndex++;
}
return uglyNum[index - 1];
}
不用加减乘除做加法
思路:
public class Solution {
public int Add(int num1,int num2) {
int sum = 0;
int carry = 0;
do {
sum = num1 ^ num2;
carry = (num1 & num2) << 1;
num1 = sum;
num2 = carry;
} while (carry != 0);
return num1;
}
}
不使用新变量交换两个变量的值
// 基于加减法
a = a + b;
b = a - b;
a = a - b;
// 基于异或
a = a ^ b;
b = a ^ b;
a = a ^ b;
抽象建模
44.扑克牌的顺子
import java.util.*;
public class Solution {
public boolean isContinuous(int [] numbers) {
if (numbers == null || numbers.length != 5) return false;
Arrays.sort(numbers);
// 统计大王的个数
int king = 0;
for (int i : numbers) {
if (i == 0) king++;
else break;
}
// 统计不连续间隔
int gaps = 0;
// i=king的个数+1,从而跳过王
for (int i = king+1; i < numbers.length; i++) {
if (numbers[i] == numbers[i - 1]) return false; // 出现对子,不可能是顺子
gaps += numbers[i] - numbers[i - 1] - 1;
}
return gaps <= king;
}
}