题目:一个长度为N的数,组中的所有数字都是在0~N-1的范围内,数组中某些数字是重复的,但是不知道有几个数的重复,也不知道每个数字重复了几次,请找出数组中任意一个重复的数字作为返回值。
public class Main {
/**
* @param arr 目标数组
* 时间复杂度:O(N)
* 空间复杂度:O(1)
*/
public static Integer repetition(int[] arr) {
for (int i = 0; i < arr.length; i++) {
while (arr[i] != i) {
// 若果没有这样的return 语句那么可能导致该循环不会退出
// 该条件的意思是已经存在arr[i]为下标值为arr[i]的值,此时出现俩个arr[i]
if (arr[arr[i]] == arr[i]) {
return arr[i];
}
// 交换此时下标为i和下标为arr[i]的值
int temp = arr[i];
arr[i] = arr[temp];
arr[temp] = temp;
}
}
return null;
}
}
思考:输出数组中缺失和重复的元素。
public class Main {
// 存储数组中重复出现的元素
public static HashSet<Integer> repetitionHashSet = new HashSet<>();
// 存储数组中在0~N-1之间缺失的元素
public static ArrayList<Integer> restNumList = new ArrayList<>();
public static void repetition(int[] arr) {
for (int i = 0; i < arr.length; i++) {
modify(arr, i);
}
// 此时所以元素都应该在对应位置,除非不存在对应值
// 不存在的位置都会存在一个和其他值重复的值
for (int i = 0; i < arr.length; i++) {
if (arr[i] != i) {
repetitionHashSet.add(arr[i]);
restNumList.add(i);
}
}
}
private static void modify(int[] arr, int index) {
// 首先要保证arr[index]和对应的值不为index
// 另外arr[index]为下标的元素不能已经在正确位置,防止进入死循环
// 每一次运行都仅仅改变元素位置,不能改变arr中元素值
while (arr[index] != index && !(arr[arr[index]] == arr[index])) {
// 将下标为index和下标为arr[index]的两个值进行交换
int temp = arr[index];
arr[index] = arr[temp];
arr[temp] = temp;
}
}
}
问题 :在长度为N+1的数组里面的所有数字都在1~N的范围内,所以数组中至少有一个数字是重复的,请找出数组中任意一个重复数字,但不能够修改输入的数组。要求空间复杂度O( 1 )
public class Main {
/**
* @param arr 目标数组
* @return 重复值
* 时间复杂度O(NlogN)
* 空间复杂度O(1)
*/
public static Integer repetition(int[] arr) {
if (arr == null || arr.length == 0) {
return null;
}
int left = 1;
int right = arr.length - 1;
// 当left==right时,我们并不知道是否为重复数据,所以还要进行一次countRange方法调用进行判断。
while (left <= right) {
int med = ((right - left) >> 1) + left;
// 计算left..med之间数据个数
int count = countRange(arr, left, med);
if (left == right) {
if (count > 1) {
return left;
} else {
break;
}
}
// 若数据个数比left...med之间的整数个数多则定有重复值
// 根据数据个数定重复值范围,从而缩小重复数据所在范围
if (count > (med - left + 1)) {
right = med;
} else {
left = med + 1;
}
}
return -1;
}
private static int countRange(int[] arr, int left, int med) {
int count = 0;
for (int i = 0; i < arr.length; i++) {
if (left <= arr[i] && arr[i] <= med) {
++count;
}
}
return count;
}
}
题目 : 在一个二维数组中每一行都按照从左到右递增的顺序排序,每一列都按照从上到下的递增的顺序排序,完成一个函数输入这样的一个二维数组和一个整数,判断数组中是否还有这样的整数。
public class Main {
public static boolean process(int[][] arr, int x) {
// 异常条件
if (arr == null || arr.length == 0 || arr[0].length == 0 || arr[0][0] > x
|| arr[arr.length - 1][arr[0].length - 1] < x) {
return false;
}
int row = 0;
int line = 0;
// 控制范围
while (row >= 0 && row < arr.length && line >= 0 && line < arr[0].length) {
// 目标值比arr[row][line]大就有向右和向下两个方向
if (arr[row][line] < x) {
// 向右满足吗?
if (line + 1 < arr[0].length && arr[row][line + 1] < x) {
++line;
} else {
++row;
}
// 目标值比arr[row][line]小
} else if (arr[row][line] > x) {
--line;
} else {// 等于目标值
return true;
}
}
// 因为范围出界返回不存在
return false;
}
}
题目 : 请实现一个函数,把字符串中的每个空格转换为 %20
public class Main {
public static void main(String[] args) {
char[] arr = new char[100];
System.out.println(arr.length);
arr[0] = ' ';
arr[1] = 'a';
arr[2] = ' ';
arr[3] = 'a';
arr[4] = 'a';
arr[5] = 'a';
arr[6] = ' ';
arr[7] = 'a';
arr[8] = 'a';
arr[9] = ' ';
process(arr, 10, 100);
}
/**
* @param arr 数组对象
* @param thisLen 当前数组长度
* @param maxLen arr最大容量
* 时间复杂度O(N)
* 空间复杂度O(1)
*/
public static void process(char[] arr, int thisLen, int maxLen) {
if (arr == null || maxLen <= 0 || thisLen <= 0) {
return;
}
// 统计空格数量
int sumK = 0;
for (int i = 0; i < thisLen; i++) {
if (arr[i] == ' ') {
++sumK;
}
}
// 计算新数组长度
int newLen = sumK * 2 + thisLen;
if (newLen > maxLen) {
return;
}
// 两个指针进行遍历
int index = thisLen - 1;
int newIndex = newLen - 1;
while (index >= 0) {
if (arr[index] == ' ') {
arr[newIndex--] = '0';
arr[newIndex--] = '2';
arr[newIndex] = '%';
} else {
arr[newIndex] = arr[index];
}
--index;
--newIndex;
}
}
}
题目 : 输入一个链表的头节点,从尾到头反过来打印出每个节点的值。
public class Main {
//栈实现
public static void process_1(Node header) {
if (header == null) {
return;
}
Stack<Node> stack = new Stack<>();
Node tail = header;
while (tail != null) {
stack.push(tail);
tail = tail.nextNode;
}
while (!stack.isEmpty()) {
System.out.println(stack.pop().num);
}
}
// 递归实现
public static void process_2(Node header) {
if (header == null) {
return;
}
process_2(header.nextNode);
System.out.println(header.num);
}
// 反序链表实现
public static void process_3(Node header) {
if (header == null) {
return;
}
Node pre = null;
Node tail = header;
Node post = null;
while (tail != null) {
post = tail.nextNode;
tail.nextNode = pre;
pre = tail;
tail = post;
}
header = pre;
while (pre != null) {
System.out.println(pre.num);
pre = pre.nextNode;
}
pre = null;
tail = header;
post = null;
while (tail != null) {
post = tail.nextNode;
tail.nextNode = pre;
pre = tail;
tail = post;
}
}
public static class Node {
int num;
Node nextNode;
public Node(int num, Node nextNode) {
this.num = num;
this.nextNode = nextNode;
}
}
}
题目 : 输入某二叉树的前序遍历和中序遍历的结果,请重建二叉树。假设输入的前驱和后继的节点都没有重复的数字,函数要返回树的根节点 。
public class Main {
public static Node construction(int[] pre, int[] in) {
if (pre == null || in == null ||
pre.length == 0 || in.length == 0 || pre.length != in.length) {
return null;
}
return construction(pre, 0, pre.length - 1, in, 0, in.length - 1);
}
/**
* @param pre 前序遍历
* @param preStart 前序遍历部分的首元素
* @param preEnd 前序遍历部分的尾元素
* @param in 中序遍历
* @param inStart 中序遍历部分的首元素
* @param inEnd 中序遍历部分的尾元素
* @return pre[preStart]对应的当前节点
* 注意 : pre[preStart...preEnd] 和 in[inStart...inEnd]中包含的元素完全相同
*/
private static Node construction(int[] pre, int preStart, int preEnd, int[] in, int inStart, int inEnd) {
// if(preStart>preEnd)return null;添加上这一句可以代替左右子树的节点存在判断
// 建立当前节点pre[preStart]
Node root = new Node(pre[preStart]);
if (preStart == preEnd) {
return root;
}
// 根据pre[preStart]查找in中数据位置记作数据A,
// A前面的元素个数用leftNum记录
int leftNum = 0;
int tail = inStart;
while (pre[preStart] != in[tail]) {
++leftNum;
++tail;
}
/*
* 实际上在该函数一开始时加上一个判断
* if(preStart>preEnd)return null;
* 也可以避免root左右子树不存在情况,不过这样的方式更好理解
*
* 另外注意观察左右子树建立时的范围:
* 前序遍历:
* pre[preStart]为当前根节点
* pre[preStart+1...preStart + leftNum]为左子树范围
* pre[preStart + leftNum + 1...preEnd]为右子树范围
* 中序遍历:
* in[inStart...inStart + leftNum - 1]为左子树范围
* in[inStart + leftNum]为当前根节点
* in[inStart + leftNum + 1...inEnd]为右子树范围
*/
// 若leftNum > 1 说明当前root有左子树就设置
if (leftNum > 0) {
root.leftNode = construction(pre, preStart + 1, preStart + leftNum,
in, inStart, inStart + leftNum - 1);
}
// 判断是否存在右子树
if (leftNum < preEnd - preStart) {
root.rightNode = construction(pre, preStart + leftNum + 1, preEnd,
in, inStart + leftNum + 1, inEnd);
}
return root;
}
public static class Node {
int num;
Node leftNode;
Node rightNode;
public Node(int num) {
this.num = num;
}
}
}
题目:给定一颗二叉树和其他一个节点,如何找到中序遍历的下一个节点?另外组成该树的节点类型记录了该节点的父节点。
public class Main {
public static Node nextNode(Node node) {
if (node == null) {
return null;
}
// 若该节点存在右节点
if (node.rightNode != null) {
return leftestNode(node.rightNode);
}
// 没有右节点,那么下一个元素必定为父类节点以及以上节点中记为A
// 并且该node节点定为在A的左子树中,否则不存在
return leftChildOfParent(node);
}
// 查找在那个节点的左子树中
private static Node leftChildOfParent(Node node) {
Node parent = node.parentNode;
while (parent != null && parent.rightNode == node) {
node = parent;
parent = parent.parentNode;
}
return parent;
}
// 查找最左节点
private static Node leftestNode(Node rightNode) {
Node tail = rightNode;
while (tail.leftNode != null) {
tail = tail.leftNode;
}
return tail;
}
public static class Node {
int num;
Node leftNode;
Node rightNode;
Node parentNode;
public Node(int num) {
this.num = num;
}
}
}
题目 : 用两个栈结构完成队列的功能,实现两个方法,appendTail : 增加元素,deleteHeader:删除元素。
public class Main {
Stack<Integer> addStack;
Stack<Integer> deleteStack;
public Main() {
this.addStack = new Stack<Integer>();
this.deleteStack = new Stack<Integer>();
}
public void appendTail(int num) {
addStack.push(num);
}
public Integer deleteHeader() {
//一定要将deleteStack中元素删除完才能从addSrtack中取出元素给deleteStack.
if (!deleteStack.isEmpty()) {
return deleteStack.pop();
}
if (addStack.isEmpty()) {
return null;
}
//每次从addStack中删除到deleteStack中时,定将addStack中元素删除完
while (!addStack.isEmpty()) {
deleteStack.add(addStack.pop());
}
return deleteStack.pop();
}
}
题目 : 用两个队列结构完成栈的功能,实现两个方法,add: 增加元素,poll:删除元素。
public class Main {
private ArrayDeque<Integer> deque1;
private ArrayDeque<Integer> deque2;
private boolean hasNum_1 = true;
private boolean hasNum_2 = false;
private boolean flag;//为true时,表示deque1有元素,false时表示deque2有元素。
public Main() {
deque1 = new ArrayDeque<>();
deque2 = new ArrayDeque<>();
flag = hasNum_1;
}
public void add(int num) {
if (flag) {
deque1.add(num);
}else {
deque2.add(num);
}
}
public Integer poll() {
if (flag) {
if (deque1.isEmpty()) {
return null;
}
while (deque1.size()!=1) {
deque2.push(deque1.poll());
}
flag=hasNum_2;
return deque1.poll();
}else {
if (deque2.isEmpty()) {
return null;
}
while (deque2.size()!=1) {
deque1.push(deque2.poll());
}
flag=hasNum_1;
return deque2.poll();
}
}
}
问题:求第N个斐波那契数。
public class Main {
//递归实现
private static Integer process(int N) {
if (N==1||N==2) {
return 1;
}
return process(N-1)+process(N-2);
}
//循环实现
private static Integer process(int N) {
if (N<=0) {
return null;
}
if (N<3) {
return 1;
}
int pre=1;
int post=1;
int res=0;
for (int i = 2; i < N; i++) {
res=pre+post;
pre=post;
post=res;
}
return res;
}
}
一只青蛙一次可以跳上 1 级台阶,也可以跳 2 级台阶,求青蛙正好跳 N 阶台阶有多少种跳法。
public class Main {
private static Integer process(int N) {
if (N<=0) {
return null;
}
if (N==1) {
return 1;
}
if (N==2) {
return 2;
}
int pre=1;
int post=2;
int res=0;
for (int i = 2; i < N; i++) {
res=pre+post;
pre=post;
post=res;
}
return res;
}
}
题目:将一个已排序数组的最开始的若干个元素搬到数组末尾,输出该旋转后的数组的最小值,空间复杂度 O(1)。例如{3,4,5,1,2,2},最小值为 1
public class Main {
public static Integer process(int[] arr) {
if (arr == null || arr.length == 0) {
return null;
}
int tail_1 = 0;
int tail_2 = arr.length - 1;
// 如果条件满足,说明整体有序,arr是一个有序数组。
if (arr[tail_1] < arr[tail_2]) {
return arr[0];
}
// 因为tail_1指向左有序序列,tail_2指向右有序序列,定相差 >= 1 ,当相差为 1 时,tail_2指向的就是最小值
while (tail_2 - tail_1 != 1) {
int med = ((tail_2 - tail_1) >> 1) + tail_1;
//若果三值同在后续过程中无法判断缩小范围的方向,所以从此要进行线性查找。
//像 { 3, 1, 3, 3, 3, 3 } { 3, 3, 3, 3, 1, 3 } 的数组不能确定向左还是向右.
if (arr[tail_1] == arr[tail_2] && arr[tail_1] == arr[med]) {
return process(arr, tail_1, tail_2);
}
// 这样的判断和移动范围方式,使tail_1始终在arr前面的有序部分,tail_2始终在arr后面的有序部分
// 因为在进行该判读前已经判断arr[tail_1],arr[tail_2],arr[med]三个值不全同。
// 所以在进行判断时,如果arr[med] == arr[tail_1]成立,那么arr[med] > arr[tail_2]成立,所以tail_1向右动
// 等号必须在tail_1向左移动的条件上。
if (arr[med] >= arr[tail_1]) {
tail_1 = med;
} else if (arr[med] < arr[tail_2]) {
tail_2 = med;
}
}
return arr[tail_2];
}
private static Integer process(int[] arr, int tail_1, int tail_2) {
int min = Integer.MAX_VALUE;
// 在当前范围中查找最小值。
for (int i = tail_1; i <= tail_2; i++) {
min = Math.min(min, arr[i]);
}
return min;
}
}
简洁代码:
class Solution {
public int findMin(int[] nums) {
int left = 0, right = nums.length - 1;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] < nums[right]) right = mid;
else if (nums[mid] > nums[right]) left = mid + 1;
else right--;
}
return nums[right];
}
}
题目 : 请设计请设计一个函数用来判断在矩阵中是否存在一条包含某字符串所有字符的路径,路径可以从矩阵中的任意一个位置开始,每部可以在矩阵中的上下左右四个方向移动,如果一条路径已经经过了矩阵的某一个格子,那么该路径就不能够再次进入该格子。
public class Main {
public static boolean process(char[][] matrix, char[] str) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0 || str.length == 0) {
return false;
}
int rows = matrix.length;
int line = matrix[0].length;
// 用于记录是否走过该路径
boolean[][] isVisited = new boolean[rows][line];
int pathLen = 0;
// 对于每一个元素进行起始的判断
for (int i = 0; i < rows; i++) {
for (int j = 0; j < line; j++) {
if (hasSuccessPath(matrix, i, j, str, 0, isVisited)) {
return true;
}
}
}
return false;
}
/**
* @param matrix 元素矩阵
* @param row 判断matrix中行位置元素
* @param line 判断matrix中列位置元素
* @param str 寻找的目标串
* @param index 目标串的第几个元素
* @param isVisited 记录是否走过的表格
* @return 该路径是否可行
*/
private static boolean hasSuccessPath(char[][] matrix, int row, int line, char[] str, int index,
boolean[][] isVisited) {
// 此时说明index前面的所有元素均已经匹配成功
if (str.length == index) {
return true;
}
// 不符合条件返回不通行
if (row >= matrix.length || line == matrix[0].length || row < 0 || line < 0 || isVisited[row][line]
|| matrix[row][line] != str[index]) {
return false;
}
++index;
// 标记该路已经走过
isVisited[row][line] = true;
// 向四个方向均进行尝试
boolean hasPath = hasSuccessPath(matrix, row + 1, line, str, index, isVisited)
|| hasSuccessPath(matrix, row, line + 1, str, index, isVisited)
|| hasSuccessPath(matrix, row - 1, line, str, index, isVisited)
|| hasSuccessPath(matrix, row, line - 1, str, index, isVisited);
// 若没有成功将isVisited值进行还原避免影响后续判断,若为真那么在该步骤结束时循环,不会对后来的产生影响
if (!hasPath) {
isVisited[row][line] = false;
}
return hasPath;
}
}
题目: 地上有一个M行N列的方格,一个机器人从坐标( 0, 0 )的个格子开始移动,他每次可向左右上下四个方向移动一个格子,但不能进入行坐标和列坐标的各个位数之和大于K值,并且到达每个格子之前必定可以通过其他格子到达该格子。例如: k=18,机器人可以进入(35,37),3+5+3+7=18<=18能进入,(36,38),3+6+3+8=20>18,不能进入。
public class Main {
public static int process(int k, int rows, int lines) {
if (k < 0 || rows <= 0 || lines <= 0) {
return 0;
}
boolean[][] isVisited = new boolean[rows][lines];
return movingCount(k, rows, lines, 0, 0, isVisited);
}
/**
* @param k 限制条件k
* @param rows 矩阵总行数
* @param lines 矩阵总列数
* @param i 当前元素行数量
* @param j 当前元素列数量
* @param isVisited 是否已经访问过
* @return 可以选择的个数
*/
private static int movingCount(int k, int rows, int lines, int i, int j, boolean[][] isVisited) {
int count = 0;
// 如果满足限制条件就进行标记,并以此为展开进行递归搜索
if (canVisited(k, rows, lines, i, j, isVisited)) {
isVisited[rows][lines] = true;
count += movingCount(k, rows, lines, i + 1, j, isVisited)
+ movingCount(k, rows, lines, i - 1, j, isVisited)
+ movingCount(k, rows, lines, i, j + 1, isVisited)
+ movingCount(k, rows, lines, i, j - 1, isVisited);
}
return count;
}
private static boolean canVisited(int k, int rows, int lines, int i, int j, boolean[][] isVisited) {
if (i >= 0 && i < rows && j >= 0 && j < lines && !isVisited[i][j] && getDigitSum(i) + getDigitSum(j) <= k) {
return true;
}
return false;
}
// 计算num和各位数字之和
private static int getDigitSum(int num) {
int sum = 0;
while (num > 0) {// 不能 = 0
sum += num % 10;
num /= 10;
}
return sum;
}
}
题目: 给你一根长度为N的绳子,请把绳子剪成M段 , M和N都是整数且均大于1,每段绳子的长度记作K1,K2 … Km,请问它们相乘的最大乘积是多少?
递归实现
public class Main {
public static int processMain(int N) {
if (N < 2) {
return 0;
} else if (N == 2) {
return 1;
} else if (N == 3) {
return 2;
}
return process(N);
}
public static int process(int N) {
if (N == 1) {
return 1;
} else if (N == 2) {
return 2;
} else if (N == 3) {
return 3;
}
int max = 0;
for (int i = 1; i <= N / 2; i++) {
max = Math.max(max, process(i) * process(N - i));
}
return max;
}
}
动态规划
public class Main {
public static int processMain(int N) {
if (N < 2) {
return 0;
} else if (N == 2) {
return 1;
} else if (N == 3) {
return 2;
}
int[] maxValues = new int[N + 1];
maxValues[0] = 0;
maxValues[1] = 1;
maxValues[2] = 2;
maxValues[3] = 3;
int max = 0;
//动态规划主体思想
for (int i = 4; i <= N; i++) {
max = 0;
//将N看成i计算i的最大值
for (int j = 1; j <= i / 2; j++) {
max = Math.max(max, maxValues[j] * maxValues[i - j]);
}
maxValues[i] = max;
}
//返回该值
return maxValues[N];
}
}
贪心思想
public class Main {
public static int processMain(int N) {
if (N < 2) {
return 0;
} else if (N == 2) {
return 1;
} else if (N == 3) {
return 2;
}
// 尽可能多的使用3为划分
int timesOf3 = N / 3;
// 若最后剩余1,此时应该将之前分开的3.1转换成2.2,这样就能最大限度的进行转化成最大值。
if (N % 3 == 1) {
timesOf3 -= 1;
}
// 2需要出现的次数
int timesOf2 = (N - timesOf3 * 3) / 2;
return (int) Math.pow(3, timesOf3) * (int) Math.pow(2, timesOf2);
}
}
题目:请实现一个函数,输入一个整数,返回该值二进制下多包含1 的个数。
位运算右移输入整数
public class Main {
public static int processMain(int N) {
int num=0;
// 指定运算32次,处理负数
for (int i = 0; i < 32; i++) {
if ((N & 1) == 1) {
++num;
}
N>>=1;
}
return num;
}
}
位运算左移1
public class Main {
public static int processMain(int N) {
int num = 0;
int flag = 1;
while (flag != 0) {
if ((flag & N) == flag) {
++num;
}
flag <<= 1;
}
return num;
}
}
将输入整数依次减1,拆分该整数的1
public class Main {
public static int processMain(int N) {
int num = 0;
while (N != 0) {
++num;
N = (N - 1) & N;// 每次运行都会将一个1消去,
}
return num;
}
}
题目:给定一个整数 N , 打印从1到N为整数的最大值。
public class Main {
public static void main(String[] args) {
processMain(3);
}
public static void processMain(int N) {
if (N <= 0) {
return;
}
char[] arr = new char[N];
Arrays.fill(arr, '0');
// 没有到达最大值就继续进行
while (!TreeIncrement(arr)) {
PrintNumbers(arr);
}
}
private static void PrintNumbers(char[] arr) {
boolean isBegin = true;
int len = arr.length;
for (int i = 0; i < len; i++) {
// 输出要从第一个不为0的位置开始,用isBegin标记开始位置
if (isBegin && arr[i] != '0') {
isBegin = false;
}
if (!isBegin) {
System.out.print(arr[i]);
}
}
System.out.println();
}
private static boolean TreeIncrement(char[] arr) {
int nTakeOver = 0;// 进位标记 1
int len = arr.length;
for (int i = len - 1; i >= 0; --i) {
char nSum = (char) (arr[i] + nTakeOver);
// 最低位要 +1
if (i == len - 1) {
nSum++;
}
if (nSum - '0' == 10) {
// 最高位变成10了,此时说明溢出了,就返回
if (i == 0) {
return true;
} else {
// 该位变成是10了,要进位,进行下一步操作。
nTakeOver = 1;// 进位 1
arr[i] = '0';// 该值变成 0
}
} else {
// 该值不为10,所以允许,直接返回
arr[i] = nSum;
return false;
}
}
return false;
}
}
题目: 在O(1)时间内删除节点,得等单链表的·头指针个一个结点指针,定义一个函数,在规定时间复杂度内删除节点。删除节点定在该链表内。
public class Main {
public static void processMain(Node header, Node deleteNode) {
if (header == null || deleteNode == null) {
return;
}
//删除节点不是尾节点,通过复制内容直接删除下一个节点
if (deleteNode.nextNode != null) {
deleteNode.num = deleteNode.nextNode.num;
deleteNode.nextNode = deleteNode.nextNode.nextNode;
} else if (header == deleteNode) {//header只有一个结点,并且就是删除的节点
header = null;
} else {//删除的借点为尾节点,只能进行遍历查找
Node tail = header;
while (tail.nextNode != deleteNode) {
tail = tail.nextNode;
}
tail.nextNode = null;
}
}
public static class Node {
int num;
Node nextNode;
}
}
题目:在一个排序链表中,如何删除重复节点。
public class Main {
public static void processMain(Node header) {
if (header==null) {
return;
}
Node tail=header;
Node preNode=header;
while (preNode.nextNode!=null) {
Node thisNode=preNode.nextNode;
if (preNode.num!=thisNode.num) {
preNode=thisNode;
}else {
preNode.nextNode=thisNode.nextNode;
}
}
}
public static class Node {
int num;
Node nextNode;
}
}
题目:输入一个整数数组,实现一个函数来调整该数组中的数字顺序,使得所有的奇数位于数字的前半部分,所有的偶数位位于数组的后半部分。
public class Main {
public static void processMain(int [] arr) {
if (arr==null||arr.length==0) {
return;
}
int left=0;
int right=arr.length-1;
while (left<right) {
while (left<right&&!cmp(arr,left)) {
left++;
}
while (left<right&&cmp(arr, right)) {
right--;
}
if (left<right) {
int temp=arr[left];
arr[left]=arr[right];
arr[right]=temp;
}
}
}
private static boolean cmp(int[] arr, int left) {
return (arr[left]&1)==0;
}
}
题目:给定一个单链表,和整数 K,返回单链表中倒数的第K个节点。
public class Main {
public static Node processMain(Node header, int k) {
if (header == null) {
return null;
}
Node tailNode = header;
int i = 0;
// 保证节点存在
while (tailNode != null && i < k) {
tailNode = tailNode.nextNode;
}
// 因为节点不存在,说明该链表一共都不存在那么多节点
if (tailNode == null) {
return null;
}
// 两个指针同时前进,直到前面的到达链表末端
Node preNode = header;
while (tailNode != null) {
tailNode = tailNode.nextNode;
preNode = preNode.nextNode;
}
return preNode;
}
static class Node {
int value;
Node nextNode;
}
}
题目:给定一个链表的头节点,该链表包含一个环,返回该入环节点。
public class Main {
public static Node processMain(Node header) {
if (header == null) {
return null;
}
boolean flag = false;
Node quick = header;
Node slow = header;
while (quick.nextNode != null && quick.nextNode.nextNode != null) {
slow = slow.nextNode;
quick = quick.nextNode.nextNode;
// 存在环,就退出循环
if (slow == quick) {
flag = true;
break;
}
}
// 无环就返回null
if (!flag) {
return null;
}
// 走相同的步数找入环节点
Node tailNode = header;
while (tailNode != slow) {
tailNode = tailNode.nextNode;
slow = slow.nextNode;
}
// 相交节点就是入环节点
return tailNode;
}
static class Node {
int value;
Node nextNode;
}
}
题目:给定一个链表的头结点,返回链表反转后的头结点。
public class Main {
public static Node processMain(Node header) {
if (header == null) {
return null;
}
Node preNode = null;
Node tempNode = null;
Node thisNode = header;
while (thisNode != null) {
tempNode = thisNode.nextNode;
thisNode.nextNode = preNode;
preNode = thisNode;
thisNode = tempNode;
}
return preNode;
}
static class Node {
int value;
Node nextNode;
}
}
输入两个有序链表的头结点,返回合并后的有序链表头结点。要求空间复杂度为O(1)。
递归实现
public class Main {
public static Node merge(Node header_1, Node header_2) {
if (header_1 == null) {
return header_2;
} else if (header_2 == null) {
return header_1;
}
Node mergeHeader = null;
if (header_1.num < header_2.num) {
mergeHeader = header_1;
mergeHeader.nextNode = merge(header_1.nextNode, header_2);
} else {
mergeHeader = header_2;
mergeHeader.nextNode = merge(header_1, header_2.nextNode);
}
return mergeHeader;
}
public static class Node {
int num;
Node nextNode;
}
}
循环实现
public class Main {
public static Node merge(Node header_1, Node header_2) {
if (header_1 == null) {
return header_2;
} else if (header_2 == null) {
return header_1;
}
Node mergeHeader = header_1.num < header_2.num ? header_1 : header_2;
Node tailNode = mergeHeader;
Node tempNode;
while (header_1 != null && header_2 != null) {
if (header_1.num < header_2.num) {
tailNode.nextNode = header_1;
header_1 = header_1.nextNode;
} else {
tailNode.nextNode = header_2;
header_2 = header_2.nextNode;
}
tailNode = tailNode.nextNode;
}
tailNode.nextNode = header_1 == null ? header_2 : header_1;
return mergeHeader;
}
public static class Node {
int num;
Node nextNode;
}
}
输入两棵二叉树根节点A、B,判断 B 是不是 A 的子树。
public class Main {
public static boolean subTree(Node A, Node B) {
boolean isSub = false;
if (A != null && B != null) {
if (A.num == B.num) {
isSub = com(A, B);// 判断B是否为从A开始的子树
}
// 未成功
if (!isSub) {
isSub = subTree(A.leftNode, B);// 向左方向尝试
}
// 未成功
if (!isSub) {
isSub = subTree(A.rightNode, B);// 向右方向尝试
}
}
return isSub;
}
private static boolean com(Node a, Node b) {
if (b == null) {
return true;
}
if (a == null) {
return false;
}
if (a.num != b.num) {
return false;
}
// 保证每一子步均成立
return com(a.leftNode, b.leftNode) && com(a.rightNode, b.rightNode);
}
public static class Node {
int num;
Node leftNode;
Node rightNode;
}
}
题目:请完成一个函数,输入一个树的根节点,返回该二叉树镜像的根节点。
方法一
public class Main {
public static Node resverse(Node header) {
if (header==null) {
return null;
}
//为什么要先存起来呢?因为在下一步运行时,会将header.leftNode指向改变,造成无法定位之前的Node节点
Node tempNode=header.leftNode;
header.leftNode=resverse(header.rightNode);
header.rightNode=resverse(tempNode);
return header;
}
public static class Node {
int num;
Node leftNode;
Node rightNode;
}
}
方法二
public class Main {
public static void resverse(Node header) {
if (header==null) {
return;
}
if (header.leftNode==null&&header.rightNode==null) {
return;
}
Node tempNode=header.leftNode;
header.leftNode=header.rightNode;
header.rightNode=tempNode;
if (header.leftNode!=null) {
resverse(header.leftNode);
}
if (header.rightNode!=null) {
resverse(header.rightNode);
}
}
public static class Node {
int num;
Node leftNode;
Node rightNode;
}
}
题目:请完成一个函数,用于判断二叉树是否是对称的,给定根节点,输出boolean数据。(所有的非满二叉树均不对称)
public class Main {
public static boolean isSym(Node header) {
return isSym(header,header);
}
private static boolean isSym(Node tail1, Node tail2) {
if (tail1==null&&tail2==null) {
return true;
}
if(tail1==null||tail2==null) {
return false;
}
if(tail1.num!=tail2.num) {
return false;
}
// 两个方向均保证步调相反
return isSym(tail1.leftNode,tail2.rightNode)&&
isSym(tail1.rightNode,tail2.leftNode);
}
public static class Node {
int num;
Node leftNode;
Node rightNode;
}
}
题目:顺时针打印矩阵。
class Main {
public static void print(int[][] arr) {
int firstRow = 0;
int firstLine = 0;
int secondRow = arr.length - 1;
int secondLine = arr[0].length - 1;
// 宏观限制单词打印的限制条件
while (firstLine <= secondLine && firstRow <= secondRow) {
print(arr, firstRow++, firstLine++, secondRow--, secondLine--);
}
}
// 宏观的单词输出函数
private static void print(int[][] arr, int firstRow, int firstLine, int secondRow, int secondLine) {
if (firstLine == secondLine) {
for (int i = firstRow; i <= secondRow; i++) {
System.out.print(arr[i][firstLine] + " ");
}
} else if (firstRow == secondRow) {
for (int i = firstLine; i <= secondLine; i++) {
System.out.print(arr[firstRow][i] + " ");
}
} else {
int curR = firstRow;
int curL = firstLine;
while (curL != secondLine) {
System.out.print(arr[firstRow][curL++] + " ");
}
while (curR != secondRow) {
System.out.print(arr[curR++][secondLine] + " ");
}
while (curL != firstLine) {
System.out.print(arr[secondRow][curL--] + " ");
}
while (curR != firstRow) {
System.out.print(arr[curR--][firstLine] + " ");
}
}
}
}
题目:请完成一个栈结构,能随时获取栈中最小元素,push、pop、getMin函数的时间复杂度均为 O(1)
class MyStack {
private Stack<Integer> stack;
private Stack<Integer> minStack;
public MyStack() {
this.stack = new Stack<>();
this.minStack = new Stack<>();
}
public void push(Integer x) {
stack.add(x);
minStack.add(!minStack.isEmpty() && minStack.peek() < x ? minStack.peek() : x);
}
public Integer pop() {
if (stack.isEmpty()) return null;
minStack.pop();
return stack.pop();
}
public int getMin() {
return minStack.peek();
}
}
题目:给定两个整数序列,第一个为压入顺序,在压入过程中随时可以弹出元素,判断第二个序列是否可能为正确的弹出顺序。
public class Main {
public static boolean isSuccess(int[] pushARR, int[] popArr) {
if (pushARR == null || popArr == null || pushARR.length != popArr.length) {
return false;
}
int N = pushARR.length;
Stack<Integer> stack = new Stack<>();
int pushIndex = 0, popIndex = 0;
while (popIndex < N) {
while (stack.isEmpty() || stack.peek() != popArr[popIndex]) {
// 若已经输入完了还没有和popArr此时相匹配的,此时退出就返回false
// 此时的栈必定不为空,若为空:
// 上次必匹配成功了,栈中删除一个元素,此时,pushIndex, popIndex值相同
// - 若均为N,那个在上次删除后不满足:popIndex == N ,不会进入循环
// - 若 popIndex < N 不能进入该判断
if (pushIndex == N) {
return false;
}
// 如果栈空间有限制,可以在此处判断栈是否为满
// if(stack.size() > maxNum)
// return false;
stack.push(pushARR[pushIndex++]);
}
stack.pop();
popIndex++;
}
// 若退出循环 popIndex==N 、stack.isEmpty() 均成立
return true;
}
}
题目:请完成一个函数,给定头结点,横向打印二叉树。
public class Main {
public static void process(Node root) {
if (root==null) {
return;
}
Deque<Node> deque=new ArrayDeque<>();
while (!deque.isEmpty()) {
Node tempNode=deque.poll();
System.out.print(tempNode.num+" ");
// 左不空
if (tempNode.leftNode==null) {
deque.add(tempNode.leftNode);
}
// 右不空
if (tempNode.rightNode==null) {
deque.add(tempNode.rightNode);
}
}
}
public static class Node{
int num;
Node leftNode;
Node rightNode;
}
}
题目:在上一题的基础上将每一行的节点值打印在不同的行上。
public class Main {
public static void process(Node root) {
if (root == null) {
return;
}
// 遍历该层的最后一个节点
Node thisFloorLastNode = root;
// 遍历该层节点下一层的最后一个节点,用于更新thisFloorLastNode作为结束标志
Node nextFloorLastNode = null;
Deque<Node> deque = new ArrayDeque<>();
while (!deque.isEmpty()) {
Node tempNode = deque.poll();
System.out.print(tempNode.num + " ");
// 随时不管是左还是右有节点,都要更新nextFloorLastNode节点指向
if (tempNode.leftNode == null) {
deque.add(tempNode.leftNode);
nextFloorLastNode = tempNode.leftNode;
}
if (tempNode.rightNode == null) {
deque.add(tempNode.rightNode);
nextFloorLastNode = tempNode.rightNode;
}
// 该层结束,就要更新层结束标志。
if (tempNode == thisFloorLastNode) {
thisFloorLastNode = nextFloorLastNode;
nextFloorLastNode = null;
System.out.println();// 分行
}
}
}
public static class Node {
int num;
Node leftNode;
Node rightNode;
}
}
题目:实现一个函数,给定根节点,第一行节点从左向右打印,第二行从右向左打印,第三行从左向右打印,以此类推。
public class Main {
public static void process(Node root) {
if (root == null) {
return;
}
// 创建两个栈结构
Stack<Node>[] stack = new Stack[] { new Stack<>(), new Stack<>() };
int current = 0;// 当前需要遍历的层需要的栈索引
int next = 1;// 遍历(删除)stack[current]时,填充stack[next]位置的栈空间
stack[current].push(root);// 将根节点放入当前栈中
while (!stack[0].isEmpty() || !stack[1].isEmpty()) {
Node tempNode = stack[current].pop();
// 由于遍历不同层所需要的填入stack[next]顺序不同,所以要分开。
if (current == 0) {
// 不为空就要入栈
if (tempNode.leftNode != null) {
stack[next].push(tempNode.leftNode);
}
if (tempNode.rightNode != null) {
stack[next].push(tempNode.rightNode);
}
} else {
// 不为空就要入栈
if (tempNode.rightNode != null) {
stack[next].push(tempNode.rightNode);
}
if (tempNode.leftNode != null) {
stack[next].push(tempNode.leftNode);
}
}
// 若当前栈空,说明该层节点结束,进行遍历另一个栈结构
if (stack[current].isEmpty()) {
System.out.println();
// 转换栈结构的索引
current = 1 - current;
next = 1 - next;
}
}
}
public static class Node {
int num;
Node leftNode;
Node rightNode;
}
}
题目:给定一个数组,判断该数组能否转成某一个一搜索二叉树的后序遍历的便利结果,返回 boolean类型数据。
public class Main {
public static boolean process(int[] arr) {
if (arr == null || arr.length <= 2) {
return true;
}
return process(arr, 0, arr.length - 1);
}
/**
* @param arr 目标数组
* @param start 起始索引
* @param end 结尾索引
* @return 是否符合特征
*/
private static boolean process(int[] arr, int start, int end) {
// 递归结束条件
if (start == end) {
return true;
}
int gap = arr[end];
int i = start;
// 后序遍历的最后一个节点定能将搜索二叉树分成两份,左小,右大
for (; i < end; i++) {
if (arr[i] > gap) {
break;
}
}
// 此时的arr[i]为大于gap的最小值
int j = i;
// 判断arr[i...end-1]是否均比gap大
for (; j < end; ++j) {
if (arr[j] < gap) {
return false;
}
}
// 左右两份分别进行判断
// 左为小的,不包括arr[i]
boolean left = process(arr, start, i - 1);
// 右为大的,包括arr[i],但是注意不能包含arr[end],因为已经判断过了
boolean right = process(arr, i, end - 1);
// 左右均成立才整体成立
return left && right;
}
public static class Node {
int num;
Node leftNode;
Node rightNode;
}
}
题目:给定一个二叉树根节点和一个 整数 Intend ,打印二叉树中所有从根节点到叶子节点路径和为 Intend 的所有路径。
public class Main {
public static void process(Node root, int intend) {
if (root == null) {
return;
}
Stack<Node> stack = new Stack<>();
process(root, intend, 0, stack);
}
/**
* @param node 当前节点
* @param intend 目标值
* @param curNum 当前值
* @param stack 存储路径
*/
private static void process(Node node, int intend, int curNum, Stack<Node> stack) {
curNum += node.num;
stack.add(node);// 路径添加
// 为叶子节点且和目标值相同
if (node.leftNode == null && node.rightNode == null && intend == curNum) {
for (Node n : stack) {
System.out.print(n.num);
}
System.out.println();
}
if (node.leftNode != null) {
process(node.leftNode, intend, curNum, stack);
}
if (node.rightNode != null) {
process(node.rightNode, intend, curNum, stack);
}
// 路径删除
stack.pop();
}
public static class Node {
int num;
Node leftNode;
Node rightNode;
}
}
题目:给定一个链表,该链表除了有nextNode外,还有一个指针complexNode,该指针可以指向链表任意位置。给定头结点,范围复制节点后的头结点。
节点类型:
class Node {
int value;
Node nextNode;
Node complexNode;
}
借助map实现
public class Main {
public static Node copy(Node header) {
if (header == null) {
return null;
}
HashMap<Node, Node> map = new HashMap<Node, Node>();
Node tail = header;
// 遍历填入哈希表
while (tail != null) {
map.put(tail, new Node(tail.value));
tail = tail.nextNode;
}
tail = header;
// 根据哈希表一一对应情况,按照所给的链表链接情况连接复制链表。
while (tail != null) {
map.get(tail).nextNode = map.get(tail.nextNode);
map.get(tail).complexNode = map.get(tail.complexNode);
tail = tail.nextNode;
}
// 返回赋值后的链表头结点,直接在map中获取
return map.get(header);
}
class Node {
int value;
Node nextNode;
Node complexNode;
}
}
相对位置模拟map实现
public class Main {
public static Node copy(Node header) {
if (header == null)
return null;
Node tail = header;
Node n;
// 在每个原来所给链表节点后再添加一个copy节点,本质还是使用位置信息模拟了map方法
while (tail != null) {
n = tail.nextNode;
tail.nextNode = new Node(tail.value);
tail.nextNode.nextNode = n;
tail = n;
}
tail = header;
Node copyTail;
// 复制处理complexNode节点(只对原节点判断)
while (tail != null) {
copyTail = tail.nextNode;
if (tail.complexNode != null)
copyTail.complexNode = tail.complexNode.nextNode;
tail = tail.nextNode.nextNode;//该节点不为空,必定存在下一个节点
}
// 确定原链表和复制链表的头结点。
Node copyHeader = header.nextNode;// 若不提前标记会被回收
tail = header;
// 原链表和复制链表分离
while (tail != null) {
n = tail.nextNode.nextNode;
copyTail = tail.nextNode;// 复制链表的节点
if (n != null)
copyTail.nextNode = n.nextNode;// 连接复制的链表
tail.nextNode = n;// 连接原链表
tail = n;// 更新判断下一轮
}
return copyHeader;
}
class Node {
int value;
Node nextNode;
Node complexNode;
}
}
题目:输入一棵搜索二叉树,将二叉树转成双向链表的形式。leftNode为前驱结点,rightNode为后继节点。
public class Main {
public static Node process(Node root) {
Node firstNode = null;
convertNode(root, firstNode);
return firstNode;
}
/**
* @param pThisNode 当前节点
* @param pLastNode 二叉树中序遍历的上一个节点
*/
private static void convertNode(Node pThisNode, Node pLastNode) {
if (pThisNode == null) {
return;
}
// 向左寻找节点
if (pThisNode.leftNode != null) {
convertNode(pThisNode.leftNode, pLastNode);
}
// 连接前驱结点
pThisNode.leftNode = pLastNode;
// 连接上一个节点的后继节点
if (pLastNode != null) {
pLastNode.rightNode = pThisNode;
}
// 如果存在右节点,就将该节点作为前驱节点向右遍历
if (pThisNode.rightNode != null) {
convertNode(pThisNode.rightNode, pThisNode);
}
}
class Node {
int value;
Node leftNode;
Node rightNode;
}
}
题目:给定一个字符串,输出所有字符串的所有排列组合。
class Main {
public static List<String> list=new ArrayList<>();
public static void process(String string){
char[] chars = string.toCharArray();
process(chars,0);
}
private static void process(char[] chars, int i){
if (i==chars.length){//结果
list.add(new String(chars));
return;
}
boolean[] isVisited=new boolean[26];//默认只有大写字母
for (int j=i;j<chars.length;j++){
if (!isVisited[chars[j]-'A']){//是否重复
isVisited[chars[j]-'A']=true;
swap(chars,i,j);//交换
process(chars,i+1);//递归
swap(chars,i,j);//恢复
}
}
}
private static void swap(char[] chars,int i, int j) {
char c = chars[i];
chars[i]=chars[j];
chars[j]=c;
}
}
题目:给定一个随机整数数组和一个整数 k,输出数组中最小的 k个数字。
快排子过程partition实现
public class Main {
public static void process(int[] arr, int k) {
if (arr == null || arr.length == 0 || arr.length < k) {
return;
}
int left = 0;
int right = arr.length - 1;
// 以partition为分界点
int index = partition(arr, 0, right);
while (index != k - 1) {
// partition基准值大了
if (index > k - 1) {
right = index - 1;
index = partition(arr, left, right);
} else {// partition基准值小了
left = index + 1;
index = partition(arr, left, right);
}
}
// 输出数据
for (int i = 0; i < k; i++) {
System.out.println(arr[i]);
}
}
// 快排子过程
private static int partition(int[] arr, int left, int right) {
int base = arr[left];
int i = left;
int j = right;
while (i != j) {
while (i != j && arr[j] >= base) {
j--;
}
while (i != j && arr[i] <= base) {
i++;
}
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
int temp = arr[left];
arr[left] = arr[i];
arr[i] = temp;
return i;
}
}
优先队列实现
public class Main {
public static void process(int[] arr, int k) {
if (arr == null || arr.length == 0 || arr.length < k) {
return;
}
// 大根堆
PriorityQueue<Integer> deQueue = new PriorityQueue<>((o1, o2) -> Integer.compare(o2, o1));
// 保持大根堆数据等于k
for (int i : arr) {
if (deQueue.size() < k) {
deQueue.add(i);
} else if (deQueue.peek() > i) {
deQueue.poll();
deQueue.add(i);
}
}
// 输出数据
while (!deQueue.isEmpty()) {
System.out.println(deQueue.poll());
}
}
}
题目:连续给定未知个整数,可以随时以时间复杂度 O(1) 实现这些树中的中位数的获取。
class Tree {
private PriorityQueue<Double> little;// 存较大数据,取出较小元素
private PriorityQueue<Double> large;// 存较小数据,取出较大元素
public Tree() {
little = new PriorityQueue<>();
large = new PriorityQueue<>((o1, o2) -> Double.compare(o2, o1));
}
public void add(Double x) {
// 若都没有元素,优先在其中任意一个存放该元素。
if (little.size() == 0 && large.size() == 0)
large.add(x);
else {
// 往那个堆中放
if (large.peek() < x) {
little.add(x);
} else {
large.add(x);
}
// 若两个堆大小差值绝对值大于1,要调整,以便更好输出中位数。
if (little.size() - large.size() > 1)
large.add(little.poll());
else if (little.size() - large.size() < -1)
little.add(large.poll());
}
}
public Double getMed() {
if (little.size()==0&&large.size()==0)
return null;
else if (little.size() == large.size())
return (little.peek() + large.peek()) / 2;
else if (little.size() - large.size() > 0)
return little.peek();
else
return large.peek();
}
}
题目:给定一个数组吗,其中均为整数,返回数组中一个或多个连续的元素组成的累加和的最大值。时间复杂度 O(N)
public class Main {
public static Integer maxLenNum(int[] arr) {
if (arr == null) {
return null;
}
// 记录最大值
int res = Integer.MIN_VALUE;
int cur = 0;
for (int i : arr) {
// cur记录累加,现在不更新,等判断后更新。
cur += i;
res = Math.max(res, cur);
// cur记录累加和,若累加和小于0,就重置cur为0,从新开始累加。
cur = Math.max(cur, 0);
}
return res;
}
}
题目:从 1~N 所有整数中出现 1 的次数。
解决思路:
public class Main {
public static int process(String s) {
if (s == null || s.length() == 0) {
return 0;
}
char[] arr = s.toCharArray();
return process(arr, 0);
}
private static int process(char[] arr, int index) {
// 判断最高位是否为1,进而判断1在最高位出现的次数
int first = arr[index] - '0';
// 看看除了最高位外,还剩多少位,用于判断最高位和除去最高位剩余数的出现次数
int len = arr.length - index;
// 若此时是最后一位,就根据first判断≤first的值中出现1的个数。
if (len == 1 && first == 0) {
return 0;
}
if (len == 1 && first > 0) {
return 1;
}
// 记录当前最高位1出现的次数
int numFirstDigit = 0;
// 若最高位不是 > 1,那么在最高位出现的1的次数定为10的倍数
// 比如 42346 numFirstDigit就是10000~42346最高位1的个数,也就是10000~19999
// 若为1,最高位出现的次数就是根据当前最高位后面的值大小确定
// 比如 12345,就是10000~12345
if (first > 1) {
numFirstDigit = (int) Math.pow(10, len - 1);
} else if (first == 1) {
// 从0开始,别忘了+1
numFirstDigit = Integer.parseInt(new String(arr).substring(index + 1)) + 1;
}
// 当最高位为<=first情况下,从当前最高位后出现的1的个数。
// 比如 42346
// 最高位4,numOtherDigits就是2347~42346除去万位上的1的个数,万位的个数已经得出过,就是:numFirstDigit
int numOtherDigits = first * (len - 1) * ((int) Math.pow(10, len - 2));
// 根据上一个例子:numRecursive就是剩余1~2346中1出现的个数。递归来求。
int numRecursive = process(arr, index + 1);
return numFirstDigit + numOtherDigits + numRecursive;
}
}
题目:数字以012345678910111213145…的格式序列化到一个字符序列中。在这个序列中,第5位(从0开始计数)是5,第13位是1,第19位是4,等等。请写一个函数,求任意第n位对应的数字。
public class Main {
public static int process(int index) {
if (index < 0) {
return -1;
}
int digits = 1;
while (true) {
// digits位数一共有几个
int numbers = countOfIntegers(digits);
// numbers * digits表示digits位数一共占据那么多位置索引
if (index < numbers * digits) {// 是否在digits位数字的范围内
return digitAtIndex(index, digits);
}
// 迭代后的位置占据
index -= digits * numbers;
++digits;
}
}
private static int digitAtIndex(int index, int digits) {
// (int)Math.pow(10, digits-1)表示小于digits位数的数一共有多少+1
// index / digits表示此时digits位数的 第几个-1
// 我们所要得值就在number中
int number = (int) Math.pow(10, digits - 1) + index / digits;
// index % digits表示number的第几位
// indexFromRight是从右边数第几位是我们的目标值
int indexFromRight = digits - index % digits;
// 将目标值放在个位
number /= ((int) Math.pow(10, indexFromRight - 1));
// 返回个位的值
return number % 10;
}
private static int countOfIntegers(int digits) {
if (digits == 1) {
return 10;
}
int count = (int) Math.pow(10, digits - 1);
return 9 * count;
}
}
题目:给定一个数组,把数组中所有数字拼接起来排成一个数,打印能拼接出所有拼接数字中最小的数字。例如{ 3 , 32, 321 },组成最小值为321323。
public class Main {
public static void process(int[] arr) {
if (arr == null || arr.length == 0) {
return;
}
String[] strings = new String[arr.length];
// 防止溢出
for (int i = 0; i < arr.length; i++) {
strings[i] = String.valueOf(arr[i]);
}
// 按照所需排序
Arrays.sort(strings, (o1, o2) -> (o1 + o2).compareTo(o2 + o1));
for (String string : strings) {
System.out.print(string);
}
}
}
题目:给定数字字符串,按照一下规则翻译成字母,问一共有几种方式。规则:1–>‘a’、2–>‘b’、3–>‘c’、…… 、26–>‘z’ 。
public class Main {
public static int process(String s, int i) {
// 若一个不剩或这还剩一个,就返回1(保证剩的不为0),若此时不返回,说明还剩至少两个字符
if (s.length() == i || (s.length() == i + 1 && s.charAt(i) != '0'))
return 1;
if (s.charAt(i) == '0')
return 0;// 没有0开头匹配的元素
int res = process(s, i + 1);// 一个字符的
if (Integer.parseInt(s.substring(i, i + 2)) <= 26)
res += process(s, i + 2);// 若满足匹配条件,进行两个字符的
return res;// 累加的结果返回就行了
}
}
题目:给定一个整数矩阵,从左上开始到右下角,只能向下或者右移动,问路径上的最大的是多少?
暴力递归
public class Main {
public static int process(int[][] arr) {
if (arr == null || arr.length == 0 || arr[0].length == 0) {
return 0;
}
return process(arr, arr.length - 1, arr[0].length - 1, 0, 0);
}
public static int process(int[][] arr, int M, int N, int row, int line) {
if (row == M && line == N) {
return arr[row][line];
}
if (row == M) {
return process(arr, M, N, row, line + 1) + arr[row][line];
}
if (line == N) {
return process(arr, M, N, row + 1, line) + arr[row][line];
}
return Math.max(process(arr, M, N, row + 1, line), process(arr, M, N, row, line + 1)) + arr[row][line];
}
}
动态规划
public class Main {
public static int process(int[][] arr) {
if (arr == null || arr.length == 0 || arr[0].length == 0) {
return 0;
}
int M = arr.length;
int N = arr[0].length;
int[][] dp = new int[M][N];
// 临界值填入
dp[M - 1][N - 1] = arr[M - 1][N - 1];
for (int i = M - 2; i >= 0; --i) {
dp[i][N - 1] = dp[i + 1][N - 1] + arr[i][N - 1];
}
for (int i = N - 2; i >= 0; --i) {
dp[M - 1][i] = dp[M - 1][i + 1] + arr[M - 1][i];
}
// dp表根据动态规划依赖填充
for (int row = M - 2; row >= 0; --row) {
for (int line = N - 2; line >= 0; --line) {
dp[row][line] = Math.max(dp[row + 1][line], dp[row][line + 1]) + arr[row][line];
}
}
return dp[0][0];
}
}
题目:给定一个只包含小写字母的字符串,找出最长不重复在子字符串,并返回该最长子字符串的长度。例如 “asadfgsg” 最长为“sadfg”,返回 5
public class Test {
public static int maxUnique(String str) {
if (str == null || str.equals("")) {
return 0;
}
char[] chas = str.toCharArray();
//记录每个字符上次出现的位置
int[] map = new int[26];
for (int i = 0; i < 26; i++) {
map[i] = -1;// 上次出现初始为-1
}
int len = 0;// 结果值
int pre = -1;// 当前字符串最长向前伸长下标位置记录
int cur = 0;// 临时pre,用于pre的更新
for (int i = 0; i != chas.length; i++) {
// 前面的字符串最长位置,就是arr[i]上一次出现位置和满足其他字符的最长位置
pre = Math.max(pre, map[chas[i]-'a']);
cur = i - pre;// 此时字符串最长伸展位置,cur就是以arr[i]结尾的非重复最长字符串长度。
len = Math.max(len, cur);//max的len更新
map[chas[i]] = i;//记录此时字符的位置,用于下次查找重复位置
}
// 返回结果
return len;
}
}
题目:我们把只包含质因子2、3、5的数称为丑数,求正整数域上的第N个丑数。另外习惯上将1作为第一个丑数。
public class Main {
public static int process(int N) {
if (N<=0) {
return 0;
}
int[] uglyNumber=new int[N];
uglyNumber[0]=1;
int index=1;
// multiply_2_3_5三个索引指向和uglyNumber[index]大小相近的值
int multiply_2=0;
int multiply_3=0;
int multiply_5=0;
int nextUglyNum;
while (index<N) {
nextUglyNum=Math.min(uglyNumber[multiply_2]*2
,Math.min(uglyNumber[multiply_3]*3, uglyNumber[multiply_5]*5));
uglyNumber[index]=nextUglyNum;
while (multiply_2!=index&&uglyNumber[multiply_2]*2<=nextUglyNum) {
++multiply_2;
}
while (multiply_3!=index&&uglyNumber[multiply_3]*3<=nextUglyNum) {
++multiply_3;
}
while (multiply_5!=index&&uglyNumber[multiply_5]*5<=nextUglyNum) {
++multiply_5;
}
++index;
}
return uglyNumber[N-1];
}
}
题目:数组中的两个数,若前面的一个数大于后面的一个数,那么这两个数组成一个逆序对。输入一个数组,返回逆序对的个数。
public class Main {
public static int process(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
return divideTest(arr, 0, arr.length - 1, new int[arr.length]);
}
private static int divideTest(int[] arr, int left, int right, int[] temp) {
if (left < right) {
int m = (left + right) / 2;
return divideTest(arr, left, m, temp) // 左侧的总和
+ divideTest(arr, m + 1, right, temp)// 右侧的总和
+ mergeTest(arr, left, m, right, temp);// 左侧右侧组合过程中形成总和
}
return 0;
}
private static int mergeTest(int[] arr, int left, int m, int right, int[] temp) {
int i = left;
int j = m + 1;
int tempIndex = 0;
int res = 0;
while (i <= m && j <= right) {
// 和小数和就相差在大于小于符号和这里没有乘以arr[i]
res += arr[i] > arr[j] ? (right - j + 1) : 0;
temp[tempIndex++] = arr[i] < arr[j] ? arr[i++] : arr[j++];
}
while (i <= m)
temp[tempIndex++] = arr[i++];
while (j <= right)
temp[tempIndex++] = arr[j++];
System.arraycopy(temp, 0, arr, left, tempIndex);
return res;
}
}
附加题目:小和问题和逆序对问题 小和问题 在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组 的小和。求一个数组 的小和。 例子:[1,3,4,2,5] 1左边比1小的数,没有; 3左边比3小的数,1; 4左 边比4小的数,1、3; 2左边比2小的数,1; 5左边比5小的数,1、3、4、 2; 所以小和为1+1+3+1+1+3+4+2=16
public class Main {
public static int process(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
return divideTest(arr, 0, arr.length - 1, new int[arr.length]);
}
private static int divideTest(int[] arr, int left, int right, int[] temp) {
if (left < right) {
int m = (left + right) / 2;
return divideTest(arr, left, m, temp) // 左侧小数和的总和
+ divideTest(arr, m + 1, right, temp)// 右侧小数和的总和
+ mergeTest(arr, left, m, right, temp);// 左侧右侧组合过程中形成的小数和总和
}
return 0;
}
private static int mergeTest(int[] arr, int left, int m, int right, int[] temp) {
int i = left;
int j = m + 1;
int tempIndex = 0;
int res = 0;
while (i <= m && j <= right) {
// 若左小,就是会出现小数的位置。个数由右侧确定。
res += arr[i] < arr[j] ? arr[i] * (right - j + 1) : 0;
temp[tempIndex++] = arr[i] < arr[j] ? arr[i++] : arr[j++];
}
while (i <= m)
temp[tempIndex++] = arr[i++];
while (j <= right)
temp[tempIndex++] = arr[j++];
System.arraycopy(temp, 0, arr, left, tempIndex);
return res;
}
}
题目:给定两个无环单链表,并且其中有公共部分,实现一个函数,返回相遇的第一公共节点。
public class Main {
public static Node noLoop(Node head1, Node head2) {
if (head1 == null || head2 == null) {
return null;
}
Node cur1 = head1;
Node cur2 = head2;
int n = 0;
//计算链表差值
while (cur1.next != null) {
n++;
cur1 = cur1.next;
}
while (cur2.next != null) {
n--;
cur2 = cur2.next;
}
if (cur1 != cur2) {
return null;
}
cur1 = n > 0 ? head1 : head2;
cur2 = cur1 == head1 ? head2 : head1;
n = Math.abs(n);
//长的走到和短的长度同位置
while (n != 0) {
n--;
cur1 = cur1.next;
}
//判断是否有相同节点,若无就会走到最后返回null
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
public static class Node{
int value;
Node next;
}
}
题目:给定一个有序数组和一个值,返回该值在该数组中出现的次数。
public class Main {
public static int theNumOfK(int[] arr, int k) {
if (arr == null || arr.length == 0) {
return 0;
}
int first = getFirstK(arr, k);
int last = getLastK(arr, k);
if (first != -1 && last != -1) {
return last - first + 1;
}
return 0;
}
private static int getFirstK(int[] arr, int k) {
int start = 0;
int end = arr.length - 1;
while (start <= end) {
int med = (((end - start) >> 1) + start);
if (arr[med] == k) {
// 是该值,就判断是不是第一个,若是返回,否则end向左收敛
if ((med > 0 && arr[med - 1] != k) || med == 0) {
return med;
} else {
end = med - 1;
}
} else if (arr[med] > k) {
end = med - 1;
} else {
start = med + 1;
}
}
return -1;
}
private static int getLastK(int[] arr, int k) {
int start = 0;
int end = arr.length - 1;
while (start <= end) {
int med = (((end - start) >> 1) + start);
if (arr[med] == k) {
// 是该值,就判断是不是最后一个,若是返回,否则start向右收敛
if ((med < arr.length - 1 && arr[med + 1] != k) || med == arr.length - 1) {
return med;
} else {
start = med + 1;
}
} else if (arr[med] > k) {
end = med - 1;
} else {
start = med + 1;
}
}
return -1;
}
}
题目:一个长度为N-1的递增整数数组,其中每个整数都是唯一的,范围均在0~ N-1之间,那么0~N-1之间定有一个在数组中不存在的数,返回该数。
public class Main {
public static int process(int[] arr) {
if (arr == null || arr.length == 0) {
return -1;
}
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int med = right + ((right - left) >> 1);
if (med == arr[med]) {
left = med + 1;
} else {
// 结果就是下标和值不同的组合中最左位置的下标值
// 所以只在下标不同时进行判断是否为最左位置。
if (med == 0 || arr[med - 1] == med - 1) {
return med;
} else {// 不是最左位置,继续二分
right = med - 1;
}
}
}
// 程序到这里有两种情况:
// - 所有下标和数相同 left直接走到了arr.length
// - 输入的数据不符合题意
if (left == arr.length) {
return arr.length;
}
// 若不符合题意
return -1;
}
}
题目:假设一个单调递增的数组中每个元素数组唯一,实现一个函数,给定一个该类型数组,找出任意一个数组等于下标的元素。
public class Main {
public static int process(int[] arr) {
if (arr == null || arr.length == 0) {
return -1;
}
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int med = right + ((right - left) >> 1);
if (med == arr[med]) {
return med;
}
if (med < arr[med]) {
right = med - 1;
} else {
left = med + 1;
}
}
return -1;
}
}
题目:给定一个根节点,返回二叉树最大深度。
public class Main {
public static int maxDepth(Node root) {
if (root==null) {
return 0;
}
return Math.max(maxDepth(root.leftNode),maxDepth(root.rightNode))+1;
}
public static class Node {
int value;
Node leftNode;
Node rightNode;
}
}
题目:给定一个根节点,判断是否为平衡二叉树。
树类递归思路:
public class Main {
public static Data isAVL(Node head) {
if (head == null)
return new Data(0, true);
Data lData = isAVL(head.left);
Data rData = isAVL(head.right);
int height = Math.max(lData.height, rData.height) + 1;
boolean flag = lData.isAVL && rData.isAVL && Math.abs(lData.height - rData.height) <= 1;
return new Data(height, flag);
}
static class Data {
int height;
boolean isAVL;
public Data(int height, boolean is) {
this.height = height;
this.isAVL = is;
}
}
}
题目:给定一个整数数组,只有两个数出现了一次,其他数字均出现偶数次,输出这两个数出现一次的数。
public class Main {
public static void twoSingleNum(int[] arr) {
int med = 0;
for (int a : arr) {
med ^= a;// 两个不同的单数^最后得到med
}
int rightOne = med & (~med + 1);// 取出med中二进制为1的位值(必存在,因为不同值)
int med1 = 0;
for (int a : arr) {
// 对应位为1的值取出进行^最后的到两个单数中对应位为1的值
// (a&rightOne)== 0得到对应位为0
if ((a & rightOne) == rightOne) {
med1 ^= a;
}
}
System.out.println(med1);// 两个单数其中一个值
System.out.println(med ^ med1);// 两个单数令一个值
}
}
题目:给定一个整数数组,只有一个数出现了一次,其他数字均出现了三次,输出这一个只出现一次的数。
public class Main {
public static int twoSingleNum(int[] arr) {
int[] bit = new int[32];// 每一位求和
for (int a : arr) {
int b = 1;
for (int i = 31; i >= 0; --i) {
if ((a & b) != 0) {// 为1就累加
++bit[i];
}
b <<= 1;// 换位
}
}
int res = 0;
for (int i = 0; i < 32; ++i) {
res = res << 1;
res += (bit[i] % 3);// 取余数
}
return res;
}
}
题目:给定一个整数递增数组和整数s,在数组中找出两个值相加为s,若存在就返回 true,不存在返回 false。
public class Main {
public static boolean process(int[] arr, int s) {
if (arr == null || arr.length < 2) {
return false;
}
int left = 0;
int right = arr.length - 1;
while (left < right) {
int sum = arr[left] + arr[right];
if (sum == s) {
return true;
} else if (sum > s) {
--right;
} else {
++left;
}
}
return false;
}
}
题目:给定一个整数s,打印所有和为s的连续正数序列。例如输入15,有1+2+3+4+5 = 4+5+6 = 7+8 = 15
public class Main {
public static void process(int s) {
if (s < 3) {
return;
}
int small = 1;
int big = 2;
int curSum = small + big;
while (small <= s / 2) {
if (curSum == s) {
Printer(small, big);
curSum -= small;
++small;
} else if (curSum < s) {
++big;
curSum += big;
} else {
curSum -= small;
++small;
}
}
}
private static void Printer(int small, int big) {
for (int i = small; i <= big; i++) {
System.out.print(i + " ");
}
System.out.println();
}
}
题目:输入英文句子,返回单词的反转,例如:“I am a pig”,返回 “pig a am I"
public class Main {
public static String process(String s) {
if (s == null || s.length() == 0) {
return null;
}
char[] arr = s.toCharArray();
reverse(arr, 0, arr.length - 1);
int begin = 0;
int end = 0;
while (begin < arr.length) {
if (end == arr.length || arr[end] == ' ') {
// arr[begin]和arr[end-1]均为字符
reverse(arr, begin, end - 1);
// 让begin指向下一个单词的第一个或越界退出
// end指向下一个单词的第一个并向后继续遍历
begin = ++end;
} else {
++end;
}
}
return new String(arr);
}
private static void reverse(char[] arr, int left, int right) {
if (left >= right) {
return;
}
while (left < right) {
char temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
++left;
--right;
}
}
}
题目:给定一个字符串和一整数k,返回左旋转字符串,例如:k=3 “abcdefg” --> “defgabc”
public class Main {
public static String leftReverseString(String s,int k) {
if (s==null||s.length()==0) {
return null;
}
char[] arr=s.toCharArray();
reverse(arr, 0, k-1);
reverse(arr, k, arr.length-1);
reverse(arr, 0, arr.length-1);
return new String(arr);
}
private static void reverse(char[] arr, int left, int right) {
if (left >= right) {
return;
}
while (left < right) {
char temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
++left;
--right;
}
}
}
题目:给定一个数组和滑动窗口的大小,请找出所有滑动窗口里的最大值例如,如果输入数组{2, 3, 4, 2, 6, 2, 5, 1}及滑动窗口的大小3,那么一共存在6个滑动窗口,它们的最大值分别为{4,4,6,6,6,5}
class Solution {
public int[] maxSlidingWindow(int[] nums, int windowLen) {
int N = nums.length;
int[] res = new int[N - windowLen + 1];
// 实例化双端队列
Deque<Integer> deque = new ArrayDeque<>();
// 先将nums前windowLen个数按照规则填入
for (int i = 0; i < windowLen; i++) {
while (!deque.isEmpty() && nums[deque.getFirst()] <= nums[i]) {
deque.pollFirst();
}
deque.addFirst(i);// 填入的是索引,以便于唯一确定
}
res[0] = nums[deque.getLast()];
for (int i = windowLen; i < N; i++) {
// 保持双端队列中索引对应在nums中的数据是降序的。
while (!deque.isEmpty() && nums[deque.getFirst()] <= nums[i]) {
deque.pollFirst();
}
deque.addFirst(i);// 将此时数据填入
// 当前双端队列第一个元素(最大值)是否还在滑动窗口中
if (deque.getLast() == i - windowLen) {
deque.pollLast();
}
res[i - windowLen + 1] = nums[deque.getLast()];
}
return res;
}
}
public class Test {
/**
* @param N 当前元素总个数
* @param index 第index个死
* @return 最后存活的人的位次(非索引)当前该轮中的第几位
*/
public static int getLive(int N, int index) {
if (N == 1) return 1;//该1表示在最后还剩一个时的这个新一轮的索引
// 存活人在上一轮中的位次 = ( 存活人该轮中的位次 + index - 1 ) % N + 1
return (getLive(N - 1, index) + index - 1) % N + 1;
}
}
题目:假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?例如,一只股票在某时间节点的价格为{9,1,8,5,7,12,16,14)。如果我们能在价格为5的时候买在价格为16时卖出,则能收获最大的利润11。
public class Main {
public static int process(int[] arr) {
if (arr==null||arr.length<2) {
return 0;
}
// 前面出现的最小值
int min=arr[0];
// 前面存在的最大差值
int maxSub=arr[1]-arr[0];
for (int i = 1; i < arr.length; i++) {
maxSub=Math.max(maxSub, arr[i]-min);
// 計算差值后更新,因爲此時的最小值arr[i]不能減本身
min=Math.min(min,arr[i]);
}
return maxSub;
}
}
public class Main {
public static int add(int a, int b) {
int sum = a;
while (b != 0) {// 进位信息为0,此时sum就是结果
sum = a ^ b;// 不进位信息
b = (a & b) << 1;// 进位信息
a = sum;// 调整a值继续相加
}
return sum;
}
public static int sub(int a,int b) {
return add(a, add(~b, 1));// 取反加一就是一個數的相反數
}
}
public class Main {
/**
* @param header 根节点
* @param o1 节点一
* @param o2 节点二
* @return 最近共父节点
*
*/
public static Node ancestor(Node header, Node o1, Node o2) {
if (header == null || o1 == header || o2 == header)
return header;
Node lNode = ancestor(header.leftNode, o1, o2);
Node rNode = ancestor(header.rightNode, o1, o2);
// 该条件只会成功一次,返回的header就是我们所要找的节点
// 如何将这个节点返回第一次调用这个函数时?
// 由于我们不知道这个父节点是它的父节点的左还是右
// 但是我们知道成功进入该条件后的所有递归中只能出现一边为null,另一边为header节点
// 所以 返回: lNode != null ? lNode : rNode
// 另外这句话也会在找到目标节点前将o1或o2传到上一个递归中,代表着这个路径上存在o1或o2
// 当路径上没有o1或o2时,lNode和rNode均为空,随便返回一个
if (lNode != null && rNode != null)
return header;
return lNode != null ? lNode : rNode;
}
static class Node {
int value;
Node leftNode;
Node rightNode;
}
}