本文的题目均来自LeetCode的剑指offer题库
本文的分类参考自书籍《剑指offer》
代码均采用Java实现,且大多都是最优解
码这篇文章的目的是方便自己复习看,所以很多代码是经过优化的,并且几乎没有题解,只是提了提思路。如果第一次刷的不建议只看,建议看看思路然后自己去官方站做,如果看不懂可以看很多大佬的题解
点击问题标题可直达LeetCode中文站刷题!!!
空间优先:原地排序法
遍历数组,将遍历的数试探放入值正确位置,如果正确位置已经有过正确数了,且不是当前位置,说明发现了重复数了,退出
如果正确位置不是正确值,将正确值交换到正确位置
(93%,100%)
class Solution {
public int findRepeatNumber(int[] nums) {
for (int i = 0; i < nums.length; i++) {
int n = nums[i];
//try
if (n == nums[n] && n != i) return n;
//swap
int tmp = nums[i];
nums[i] = nums[n];
nums[n] = tmp;
}
//not find
return -1;
}
}
时间优先:字典
用两个字节的boolean 的数组,占用空间小
class Solution {
public int findRepeatNumber(int[] nums) {
boolean compare[] = new boolean[nums.length];
for (int i = 0; i < nums.length; i++) {
if (compare[nums[i]]) return nums[i];
compare[nums[i]] = true;
}
return -1;
}
}
从左下或者右上为起点的查找:查找状态树是BST
(双100%)
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) return false;
int m = matrix.length;
int n = matrix[0].length;
int row = 0;
int col = n - 1;
while (row < m && col >= 0) {
if (target == matrix[row][col]) return true;
else if (target > matrix[row][col]) row++;
else col--;
}
return false;
}
}
使用线程不安全的底层实现为动态数组的StringBuilder实现字符串重组
(双100%)
class Solution {
public String replaceSpace(String s) {
StringBuilder bd = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == ' ') bd.append("%20");
else bd.append(s.charAt(i));
}
return bd.toString();
}
}
class Solution {
public int[] reversePrint(ListNode head) {
ListNode pre = null;
ListNode cur = head;
int count = 0;//反转时顺便统计链表长度,以便初始化数组
while (cur != null) {
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
count++;
}
int[] res = new int[count];//初始化结果集
cur = pre;//将反转后的头指针赋值给cur
int index = 0;//数组下标指针
while (cur != null) {
res[index++] = cur.val;
cur = cur.next;
}
return res;
}
}
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
return helper(preorder, 0, preorder.length, inorder, 0, inorder.length);
}
private TreeNode helper(int[] preorder, int p_start, int p_end, int[] inorder, int i_start, int i_end) {
//p前后指针重复说明p遍历完了
if (p_start == p_end) return null;
//初始化root节点
int root_val = preorder[p_start];
TreeNode root = new TreeNode(root_val);
//root节点再中序遍历中的位置
int i_root_index = 0;
//查找位置
for (int i = i_start; i < i_end; i++) {
if (root.val == inorder[i]) {
i_root_index = i;
break;
}
}
int leftNum = i_root_index - i_start;
root.left = helper(preorder, p_start + 1, p_start + leftNum + 1, inorder, i_start, i_root_index);
root.right = helper(preorder, p_start + leftNum + 1, p_end, inorder, i_root_index + 1, i_end);
return root;
}
}
class CQueue {
Stack<Integer> in;
Stack<Integer> out;
public CQueue() {
in = new Stack<>();
out = new Stack<>();
}
public void appendTail(int value) {
in.add(value);
}
public int deleteHead() {
if (out.isEmpty()) {
while (!in.isEmpty()) {
out.add(in.pop());
}
}
return out.isEmpty() ? -1 : out.pop();
}
}
class Solution {
private int cache[];
public int fib(int n) {
cache = new int[n + 1];
return Myfib(n);
}
private int Myfib(int n) {
if (n < 2) return n;
if (cache[n] == 0) {
cache[n] = Myfib(n - 1) + Myfib(n - 2);
}
return cache[n]%1000000007;
}
}
自底向上:动态规划
(双100%)
class Solution {
public int fib(int n) {
if (n < 2) return n;
int dp0 = 0;
int dp1 = 1;
int dp2 = 1;
for (int i = 2; i <= n; i++) {
dp2 = (dp1 + dp0)%1000000007;
dp0 = dp1;
dp1 = dp2;
}
return dp2;
}
}
分支条件有点不通用,需要记一下
class Solution {
public int minArray(int[] numbers) {
int left = 0;
int right = numbers.length - 1;
while (left < right) {
int mid = (left + right) >> 1;
if (numbers[mid] > numbers[right]) {
left = mid + 1;
} else if (numbers[mid] < numbers[right]) {
right = mid;
} else {
right--;
}
}
return numbers[left];
}
}
回溯算法+dfs
遍历数组,找到等于查找字符串第一个字符的且没有访问过的位置,dfs查找
class Solution {
public boolean exist(char[][] board, String word) {
if (board == null || board.length == 0) return false;
int m = board.length;
int n = board[0].length;
boolean[][] visited = new boolean[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (!visited[i][j] && dfs(i, j, visited, board, word, 0)) {
return true;
}
}
}
return false;
}
private boolean dfs(int i, int j, boolean[][] visited, char[][] board, String word, int index) {
if (index == word.length()) {
return true;
}
//退出条件
if (i == -1 || j == -1 || i == board.length || j == board[0].length
|| visited[i][j] || board[i][j] != word.charAt(index)) {
return false;
}
visited[i][j] = true;
//尝试访问四个方向
if (dfs(i + 1, j, visited, board, word, index + 1)) return true;
if (dfs(i - 1, j, visited, board, word, index + 1)) return true;
if (dfs(i, j + 1, visited, board, word, index + 1)) return true;
if (dfs(i, j - 1, visited, board, word, index + 1)) return true;
//回溯
visited[i][j] = false;
return false;
}
}
回溯算法:
从0,0位置开始向i增长和j增长的方向深度优先遍历
class Solution {
public int movingCount(int m, int n, int k) {
boolean visited[][] = new boolean[m][n];
return dfs(0, 0, m, n, k, visited);
}
private int dfs(int i, int j, int m, int n, int k, boolean[][] visited) {
if (i == m || j == n || visited[i][j] || getDigist(i) + getDigist(j) > k) {
return 0;
}
visited[i][j] = true;
//从0,0开始,定这两个方向dfs
return 1 + dfs(i + 1, j, m, n, k, visited) + dfs(i, j + 1, m, n, k, visited);
}
//求数位和
private int getDigist(int num) {
int res = 0;
while (num != 0) {
res += num % 10;
num /= 10;
}
return res;
}
}
class Solution {
public int cuttingRope(int n) {
int[] dp = new int[n + 1];
dp[1] = 1;
dp[2] = 1;
for (int i = 3; i <= n; i++) {
for (int j = 1; j < i; j++) {
dp[i] = Math.max(dp[i], Math.max(dp[i - j] * j, j * (i - j)));
}
}
return dp[n];
}
}
贪心算法:
经推理论证可知:
n%3==0
时,最大积为所有3相乘n%3==1
时,13<22,所以将一个3同1换成2和2n%3==2
时,最大积为所有3相乘*2class Solution {
public int cuttingRope(int n) {
if (n <= 3) return n - 1;
int x = n / 3;
int y = n % 3;
if (y == 0) return (int) Math.pow(3, x);
if (y == 1) return (int) Math.pow(3, x - 1) * 2 * 2;
return (int) Math.pow(3, x) * 2;
}
}
n&(n-1)
n&(-n)
当然,通过一个标志位从最后一个开始移位比较也可以
所以这里就有多种解法,以n&(n-1)
为例
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int count = 0;
while (n != 0) {
n &= (n - 1);
count++;
}
return count;
}
}
class Solution {
public double myPow(double x, int n) {
if (n < 0) {
n = -n;
x = 1 / x;
}
return pow(x, n);
}
private double pow(double x, int n) {
if (n == 0) return 1;
double half = pow(x, n / 2);
return (n & 1) == 1 ? half * half * x : half * half;
}
}
class Solution {
public int[] printNumbers(int n) {
int count = (int) (Math.pow(10, n) - 1);
int res[] = new int[count];
for (int i = 0; i < count; i++) {
res[i] = i + 1;
}
return res;
}
}
class Solution {
public ListNode deleteNode(ListNode head, int val) {
if (head.val == val) return head.next;
ListNode cur = head;
while (cur.next != null) {
if (cur.next.val != val)cur = cur.next;
else {
cur.next = cur.next.next;
break;
}
}
return head;
}
}
dp[i][j] = dp[i-1][j-1]
.
’ : dp[i][j] = dp[i-1][j-1]
*
’:
class Solution {
public boolean isMatch(String s, String p) {
if (s == null || p == null) return false;
int m = s.length();
int n = p.length();
boolean[][] dp = new boolean[m + 1][n + 1];
dp[0][0] = true;
//"" 和p的匹配关系初始化,a*a*a*a*a*这种能够匹配空串,其他的是都是false。
// 奇数位不管什么字符都是false,偶数位为* 时则: dp[0][i] = dp[0][i - 2]
for (int i = 2; i <= n; i += 2) {
if (p.charAt(i - 1) == '*') {
dp[0][i] = dp[0][i - 2];
}
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
char sc = s.charAt(i - 1);
char pc = p.charAt(j - 1);
if (sc == pc || pc == '.') {
dp[i][j] = dp[i - 1][j - 1];
} else if (pc == '*') {
if (dp[i][j - 2]) {
dp[i][j] = true;
} else if (sc == p.charAt(j - 2) || p.charAt(j - 2) == '.') {
dp[i][j] = dp[i - 1][j];
}
}
}
}
return dp[m][n];
}
}
设置三个标志:numSeen 是否有数字,dotSeen 是否有’.’,eSeen 是否有e
.
,且之前没出现过.
和e
,dotSeen = trueclass Solution {
public boolean isNumber(String s) {
if (s == null || s.length() == 0) return false;
//去掉首位空格
s = s.trim();
boolean numFlag = false;
boolean dotFlag = false;
boolean eFlag = false;
for (int i = 0; i < s.length(); i++) {
//判定为数字,则标记numFlag
if (s.charAt(i) >= '0' && s.charAt(i) <= '9') {
numFlag = true;
//判定为. 需要没出现过.并且没出现过e
} else if (s.charAt(i) == '.' && !dotFlag && !eFlag) {
dotFlag = true;
//判定为e,需要没出现过e,并且出过数字了
} else if ((s.charAt(i) == 'e' || s.charAt(i) == 'E') && !eFlag && numFlag) {
eFlag = true;
numFlag = false;//为了避免123e这种请求,出现e之后就标志为false
//判定为+-符号,只能出现在第一位或者紧接e后面
} else if ((s.charAt(i) == '+' || s.charAt(i) == '-') && (i == 0 || s.charAt(i - 1) == 'e' || s.charAt(i - 1) == 'E')) {
//其他情况,都是非法的
} else {
return false;
}
}
return numFlag;
}
}
因为对偶数的顺序没有要求,所以只需要维护一个奇数的索引oddIndex
如果是奇数就换到奇数索引位置
class Solution {
public int[] exchange(int[] nums) {
int oddIndex = 0;
for (int i = 0; i < nums.length; i++) {
if ((nums[i] & 1) != 0) {
int tmp = nums[i];
nums[i] = nums[oddIndex];
nums[oddIndex] = tmp;
oddIndex++;
}
}
return nums;
}
}
双指针
fast指针先移动k个位置,然后fast和slow同步后移
当fast == null时,slow刚好到倒数第k个
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode fast = head;
ListNode slow = head;
while (k-- > 0) {
fast = fast.next;
}
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
这个题题库居然没有,看了下书说的其实就是环形链表问题,这里就直接放环形链表的链接了
快慢指针法(推荐)
思路:
(F+2a+b+2x)=2(F+a+x)
,,解得F=bpublic class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (true) {
if (fast == null || fast.next == null) return null;
fast = fast.next.next;
slow = slow.next;
if (fast == slow) break;
}
fast = head;
//第二阶段,找环接入点
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return fast;
}
}
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
}
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode pre = new ListNode(-1);
ListNode cur = pre;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
cur.next = l1;
l1 = l1.next;
cur = cur.next;
} else {
cur.next = l2;
l2 = l2.next;
cur = cur.next;
}
}
if (l1 != null) {
cur.next = l1;
} else {
cur.next = l2;
}
return pre.next;
}
}
针对需要以每个节点都为起点进行遍历的问题
可采用如下方式遍历
f(A){
return B ? A(left) ? A(right)
}
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
if (A == null || B == null) return false;
return helper(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B);
}
private boolean helper(TreeNode a, TreeNode b) {
if (b == null) return true;//b空了 说明比较完了 是满足的
if (a == null) return false;//b还没空a空了,说明不满足
if (a.val != b.val) return false;//值不同不满足
return helper(a.left, b.left) && helper(a.right, b.right);//下探下一层
}
}
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if (root == null) return null;
return helper(root);
}
private TreeNode helper(TreeNode root) {
if (root == null) return null;
TreeNode left = helper(root.left);
TreeNode right = helper(root.right);
root.left = right;
root.right = left;
return root;
}
}
class Solution {
public boolean isSymmetric(TreeNode root) {
if (root == null) return true;
return helper(root.left, root.right);
}
private boolean helper(TreeNode left, TreeNode right) {
if (left == null && right == null) return true;
if (left == null || right == null) return false;
if (left.val != right.val) return false;
return helper(left.left, right.right) && helper(left.right, right.left);
}
}
class Solution {
public int[] spiralOrder(int[][] matrix) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) return new int[0];
int top = 0;
int bottom = matrix.length - 1;
int left = 0;
int right = matrix[0].length - 1;
int[] res = new int[matrix.length * matrix[0].length];
int index = 0;
while (true) {
for (int i = left; i <= right; i++) res[index++] = matrix[top][i];
if (++top>bottom)break;
for (int i = top; i <= bottom; i++) res[index++] = matrix[i][right];
if (--right<left)break;
for (int i = right; i >= left; i--) res[index++] = matrix[bottom][i];
if (--bottom<top)break;
for (int i = bottom; i >= top; i--) res[index++] = matrix[i][left];
if (++left>right)break;
}
return res;
}
}
class MinStack {
Stack<Integer> minStack;
Stack<Integer> stack;
/**
* initialize your data structure here.
*/
public MinStack() {
stack = new Stack<>();
minStack = new Stack<>();
}
public void push(int x) {
stack.push(x);
if (minStack.isEmpty() || minStack.peek() >= x)
minStack.push(x);
}
public void pop() {
if (stack.pop().equals(minStack.peek()))
minStack.pop();
}
public int top() {
return stack.peek();
}
public int min() {
return minStack.peek();
}
}
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
if (pushed == null || popped == null || popped.length != pushed.length) return false;
Stack<Integer> stack = new Stack<>();
int index = 0;//popped的下标
int num = popped.length;
for (int e : pushed) {
stack.push(e);
while (index < num && !stack.isEmpty() && stack.peek() == popped[index]) {
stack.pop();
index++;
}
}
//最后一个都出栈了
return index == num;
}
}
本考点有三个问题,其实都是层次遍历的小变形,列举最复杂的一个
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
if (root == null) return new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
Deque<TreeNode> deque = new LinkedList<TreeNode>();
deque.offer(root);
while (!deque.isEmpty()) {
int size = deque.size();
LinkedList<Integer> tmp = new LinkedList<>();
for (int i = 0; i < size; i++) {
TreeNode cur = deque.poll();
if (res.size() % 2 == 1) tmp.addFirst(cur.val);
else tmp.add(cur.val);
if (cur.left != null) deque.offer(cur.left);
if (cur.right != null) deque.offer(cur.right);
}
res.add(tmp);
}
return res;
}
}
class Solution {
public boolean verifyPostorder(int[] postorder) {
if (postorder.length <= 2) return true;
return helper(postorder, 0, postorder.length - 1);
}
private boolean helper(int[] postorder, int start, int end) {
if (start >= end) return true;
int i;//拐点
for (i = start; i < end; i++) {
if (postorder[i] > postorder[end]) break;
}
//大于根节点的部分
for (int j = i; j < end; j++) {
if (postorder[j] < postorder[end]) return false;
}
//递归遍历
return helper(postorder, start, i - 1) && helper(postorder, i, end - 1);
}
}
class Solution {
List<List<Integer>> res;
public List<List<Integer>> pathSum(TreeNode root, int sum) {
if (root == null) return new ArrayList<>();
res = new ArrayList<>();
dfs(root, sum, new ArrayList<Integer>());
return res;
}
private void dfs(TreeNode root, int sum, ArrayList<Integer> list) {
if (root == null) return;
list.add(root.val);
sum -= root.val;
if (sum == 0 && root.left == null && root.right == null) {
res.add(new ArrayList<>(list));
}
dfs(root.left, sum, list);
dfs(root.right, sum, list);
list.remove(list.size() - 1);
}
}
使用额外O(n)的空间
一次遍历使用Map暂存单个的节点
二次遍历将Map中的单节点串起来
class Solution {
public Node copyRandomList(Node head) {
if (head == null) return null;
Map<Node, Node> map = new HashMap<Node, Node>();
Node cur = head;
//将所有节点存入map
while (cur != null) {
Node singleNode = new Node(cur.val);
map.put(cur, singleNode);
cur = cur.next;
}
//将map中的单个的节点串联起来
cur = head;
while (cur != null) {
if (cur.next != null) {
map.get(cur).next = map.get(cur.next);
}
if (cur.random != null) {
map.get(cur).random = map.get(cur.random);
}
cur = cur.next;
}
return map.get(head);
}
}
不使用额外空间,原地修改
一图胜千言,图自
class Solution {
public Node copyRandomList(Node head) {
if (head == null) return null;
Node l1 = head;
Node l2 = null;
//生成所有节点,并插入到原有节点的后边
while (l1 != null) {
l2 = new Node(l1.val);
l2.next = l1.next;
l1.next = l2;
l1 = l1.next.next;
}
//更新插入节点的random
l1 = head;
while (l1 != null) {
if (l1.random != null) {
l1.next.random = l1.random.next;
}
l1 = l1.next.next;
}
l1 = head;
Node l2_head = l1.next;
//新旧节点分离,生成新链表
while (l1 != null) {
l2 = l1.next;
l1.next = l1.next.next;
if (l2.next != null) {
l2.next = l2.next.next;
}
l1 = l1.next;
}
return l2_head;
}
}
要求原地操作,所以遍历完了再连接就不现实了
所以在中序遍历的时候就要把前后关系连接上
两个暂存节点,一个存要返回的链表头的值,另一个存当前节点的前一个节点的值
class Solution {
Node head = null;//暂存头结点
Node pre = null;//当前节点的前一个节点
public Node treeToDoublyList(Node root) {
if (root == null) return null;
helper(root);
return head;
}
private void helper(Node root) {
if (root == null) return;
helper(root.left);
//暂存right节点,后面头尾相接要覆盖掉
Node right = root.right;
if (pre == null) {
head = root;
} else {
pre.right = root;
root.left = pre;
}
//头尾相接
root.right = head;
head.left = root;
//前置节点后移
pre = root;
helper(right);
}
}
public class Codec {
private String results;//定义一个成员变量储存序列化结果
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
if (root == null) return "[]";//返回空串
Deque<TreeNode> deque = new LinkedList<TreeNode>();
deque.offer(root);
results = "[" + root.val;
while (!deque.isEmpty()) {
TreeNode cur = deque.poll();
if (cur.left != null) {
deque.offer(cur.left);
results += "," + cur.left.val;
} else {
results += ",null";
}
if (cur.right != null) {
deque.offer(cur.right);
results += "," + cur.right.val;
} else {
results += ",null";
}
}
results += "]";
return results;
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
if (data.length() == 2) return null;
data = data.substring(1, data.length() - 1);//去首尾括号
String[] vals = data.split(",");
Deque<TreeNode> deque = new LinkedList<TreeNode>();
TreeNode root = new TreeNode(Integer.valueOf(vals[0]));
deque.offer(root);
int i = 1;//数组下标
while (!deque.isEmpty()) {
TreeNode cur = deque.poll();
if (vals[i].equals("null")) {
cur.left = null;
} else {
cur.left = new TreeNode(Integer.valueOf(vals[i]));
deque.offer(cur.left);
}
i++;
if (vals[i].equals("null")) {
cur.right = null;
} else {
cur.right = new TreeNode(Integer.valueOf(vals[i]));
deque.offer(cur.right);
}
i++;
}
return root;
}
}
class Solution {
List<String> list;
public String[] permutation(String s) {
boolean[] visited = new boolean[s.length()];
list = new ArrayList<>();
char[] chars = s.toCharArray();
Arrays.sort(chars);
helper(chars, visited, "");
return list.toArray(new String[list.size()]);
}
private void helper(char[] chars, boolean[] visited, String str) {
if (str.length() == chars.length) {
list.add(str);
return;
}
for (int i = 0; i < chars.length; i++) {
if (visited[i]) continue;
if (i > 0 && chars[i] == chars[i - 1] && !visited[i - 1]) continue;
visited[i] = true;
helper(chars, visited, str + chars[i]);
visited[i] = false;
}
}
}
class Solution {
public int majorityElement(int[] nums) {
int count = 0;//计数器
int card = 0;
for (int num : nums) {
if (count == 0) card = num;
count += (num == card) ? 1 : -1;
}
return card;
}
}
最小或者最大k个数最容易想到堆排序
Java的优先队列默认实现是小顶堆,采用优先队列实现:
(33%,100%)
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if (arr.length < k) return arr;
//优先队列默认实现是小顶堆
PriorityQueue<Integer> queue = new PriorityQueue<>();
for (int i = 0; i < arr.length; i++) {
queue.offer(arr[i]);
}
int res[] = new int[k];
for (int i = 0; i < k; i++) {
res[i] = queue.poll();
}
return res;
}
}
采用大顶堆实现,如果堆内少于k个时才能入堆,到达k之后移除堆顶的大的元素
判断较多,理论上更快,实际运行慢了一些
(23%,100%)
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if (arr.length < k) return arr;
//优先队列实现大顶堆
PriorityQueue<Integer> heap = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
for (int i = 0; i < arr.length; i++) {
if (heap.isEmpty() || heap.size() < k || arr[i] < heap.peek()) {
heap.offer(arr[i]);
}
if (heap.size() > k) {
heap.poll();
}
}
int res[] = new int[k];
for (int i = 0; i < k; i++) {
res[i] = heap.poll();
}
return res;
}
}
一路快速排序实现
如果下标为k-1的位置被正确找到了,那么就直接返回
(87%,100%)
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if (k == 0 || arr.length == 0) return new int[0];
//最后一个参数是要查找的数组下标
return quickSearch(arr, 0, arr.length - 1, k - 1);
}
private int[] quickSearch(int[] arr, int left, int right, int k) {
int j = partition(arr, left, right);
if (j == k) {//j位置刚好是k,则j和j之前的都小于j后面的
return Arrays.copyOf(arr, j + 1);
}
return j > k ? quickSearch(arr, left, j - 1, k)
: quickSearch(arr, j + 1, right, k);
}
private int partition(int[] arr, int left, int right) {
int v = arr[left];
int j = left;
for (int i = left; i <= right; i++) {
if (arr[i] < v) {
j++;
swap(arr, i, j);
}
}
swap(arr, left, j);
return j;
}
private void swap(int[] arr, int left, int right) {
int tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
}
}
快速排序再升级,将partition变成二路快速排序
(99.6%,100%)
private int partition(int[] arr, int l, int r) {
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
swap(arr, l, (int) (Math.random() * (r - l + 1)) + l);
//暂存arr[l]
int v = arr[l];
//j为分割位
int i = l + 1, j = r;
while (true) {
//从前面开始。小于v的不变
while (i <= r && arr[i] < v) {
i++;
}
//从后面开始。大于v不变
while (j >= l + 1 && arr[j] > v) {
j--;
}
if (i > j) {
break;
}
//到这里了: arr[i]>=v arr[j]<=v
//如果等于v也要换,保证了两边相等的数目平衡
swap(arr, i, j);
i++;
j--;
}
swap(arr, l, j);
return j;
}
(执行时间有点迷 77.7%,100%)
class MedianFinder {
private PriorityQueue<Integer> maxHeap;
private PriorityQueue<Integer> minHeap;
/**
* initialize your data structure here.
*/
public MedianFinder() {
//大顶堆,
maxHeap = new PriorityQueue<>(Collections.reverseOrder());
//小顶堆
minHeap = new PriorityQueue<>();
}
public void addNum(int num) {
maxHeap.offer(num);
minHeap.offer(maxHeap.poll());
//保证大顶堆的个数>=小顶堆的个数
if (minHeap.size() > maxHeap.size()) {
maxHeap.offer(minHeap.poll());
}
}
public double findMedian() {
if (maxHeap.size() == minHeap.size()) {
return (maxHeap.peek() + minHeap.peek()) * 0.5;
}
return maxHeap.peek();
}
}
class Solution {
public int maxSubArray(int[] nums) {
int dp[] = new int[nums.length];
dp[0] = nums[0];
int res = dp[0];
for (int i = 1; i < nums.length; i++) {
dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]);
res = Math.max(dp[i], res);
}
return res;
}
}
压缩一下dp数组到一个变量
(98.7%,100%)
class Solution {
public int maxSubArray(int[] nums) {
int preMax = nums[0];
int res = preMax;
for (int i = 1; i < nums.length; i++) {
preMax = Math.max(nums[i], preMax + nums[i]);
res = Math.max(preMax, res);
}
return res;
}
}
以两种类型距离说明递归情况
last+1
(1000-1234的千位)f(pow-1)
(0-999)+f(last)
(1000-1234的非千位)f(pow-1)+last+1+f(last)
pow
(1000-1999的最高位)high*f(pow-1)
(x000-x999的非千位个数)+f(last)
(4000-4321的非千位个数)pow+high*f(pow-1)+f(last)
(双100%)
class Solution {
public int countDigitOne(int n) {
if (n <= 0) return 0;
String s = String.valueOf(n);
int high = s.charAt(0) - '0';//最高位
int pow = (int) Math.pow(10, s.length() - 1);//位数级
int last = n - high * pow;
if (high == 1) {
return last + 1 + countDigitOne(last) + high * countDigitOne(pow - 1);
} else {
return pow + countDigitOne(last) + high * countDigitOne(pow - 1);
}
}
}
(Math.pow(10, n- 1) * 9 * n);
通过计算n在哪个范围内,就能得出n对应位置是几位数,以215为例
power = 2时 ,包含了10+180 = 190的位置,190<215,所以power++
power = 3时,包含了190+2700 = 2890>215,所以确定了power = 3
所以真实对应的数字应该是100+(215-190)/3 = 108
然后计算(215-190)/3 = 1,所以去字符串108的1下标值0,返回0
(双100%)
class Solution {
public int findNthDigit(int n) {
if (n < 10) return n;
int count = 0;
int power = 1;
while (true) {
count = helper(power);
//如果满足小于count,说明n对应的是power位的数字
if (n < count) break;
n -= count;
power++;
}
//计算得到这个数字
int resNum = (int) (Math.pow(10, power - 1) + n / power);
//判断n在这个数字的第几位就返回几
return String.valueOf(resNum).charAt(n % power) - '0';
}
//求n位数字组成的串所占的范围(1位数字不超过10,2位数字不超过180,3位数字不超过2700,四位数字不超过36000)
private int helper(int power) {
if (power == 1) return 10;
return (int) (Math.pow(10, power - 1) * 9 * power);
}
}
要点:
比较a和b的大小可以比较‘a’+'b'
和‘b’+'a'
的大小,转换成字符串拼接等等长就可以进行判断了
具体的排序算法可以自己实现也可以使用系统的快排
Java中可以实现Comparator接口,然后调用String的compareTo方法逐个比较字符
(97.6%,100%)
class Solution {
public String minNumber(int[] nums) {
String[] strs = new String[nums.length];
for (int i = 0; i < nums.length; i++) {
strs[i] = String.valueOf(nums[i]);
}
Arrays.sort(strs, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return (o1 + o2).compareTo(o2 + o1);
}
});
StringBuilder bd = new StringBuilder();
for (int i = 0; i < strs.length; i++) {
bd.append(strs[i]);
}
return bd.toString();
}
}
任何情况下,dp[i] = dp[i-1]
当i位和前一位组合成两位数还满足规则时。dp[i] = dp[i-1]+dp[i-2]
class Solution {
public int translateNum(int num) {
char[] sc = String.valueOf(num).toCharArray();
int n = sc.length;
int[] dp = new int[n + 1];
dp[0] = 1;
for (int i = 1; i <= n; i++) {
//划分为1位
dp[i] += dp[i - 1];
if (i > 1) {
int a = (sc[i - 2] - '0') * 10 + (sc[i - 1] - '0');
if (a >= 10 && a <= 25) {//可划分为两位
dp[i] += dp[i - 2];
}
}
}
return dp[n];
}
}
(98.3%,100%)
class Solution {
public int maxValue(int[][] grid) {
if (grid == null || grid.length == 0 || grid[0].length == 0) return 0;
int m = grid.length;
int n = grid[0].length;
int dp[] = new int[n + 1];
for (int i = 1; i <= n; i++) {
dp[i] = dp[i - 1] + grid[0][i - 1];
}
for (int i = 1; i < m; i++) {
for (int j = 0; j < n; j++) {
dp[j + 1] = grid[i][j] + Math.max(dp[j], dp[j + 1]);
}
}
return dp[n];
}
}
class Solution {
public int lengthOfLongestSubstring(String s) {
int n = s.length();
Set<Character> set = new HashSet<>();
int resMax = 0;//最大结果
int left = 0;//左窗界
int right = 0;//右窗界
while (left < n && right < n) {
//如果不包含,就加入set,右窗口++
if (!set.contains(s.charAt(right))) {
set.add(s.charAt(right++));
resMax = Math.max(resMax, right - left);
} else {//如果包含了,set移除左窗口值,左窗口右移
set.remove(s.charAt(left++));
}
}
return resMax;
}
}
优化的滑动窗口
用长度为128的数组来记录某个元素的下一个位置
每次遍历更新左指针的位置,如果遇到了重复的值就变为重复值原来的后一个位置
(99%,100%)
class Solution {
public int lengthOfLongestSubstring(String s) {
int n = s.length();
int[] map = new int[128];
int resMax = 0;//最大结果
for (int left = 0, right = 0; right < n; right++) {
left = Math.max(map[s.charAt(right)], left);//更新left
resMax = Math.max(resMax, right - left + 1);
map[s.charAt(right)] = right + 1;//存入right的后一个位置
}
return resMax;
}
}
动态规划dp[i] = min(dp[p2] * 2, dp[p3] * 3, dp[p5] * 5);
(89%,100%)
class Solution {
public int nthUglyNumber(int n) {
int p2 = 0;
int p3 = 0;
int p5 = 0;
int dp[] = new int[n];
dp[0] = 1;
for (int i = 1; i < n; i++) {
dp[i] = Math.min(dp[p2] * 2, Math.min(dp[p3] * 3, dp[p5] * 5));
if (dp[i] == dp[p2] * 2) p2++;
if (dp[i] == dp[p3] * 3) p3++;
if (dp[i] == dp[p5] * 5) p5++;
}
return dp[n - 1];
}
}
用一个128长度的数组模拟hash表,将所有字符的个数存入hash表
第二次按照字符串字符顺序再次遍历,如果字符在hash表中值为1,返回字符
class Solution {
public char firstUniqChar(String s) {
if (s.length() == 0) return ' ';
int[] compare = new int[128];
for (int i = 0; i < s.length(); i++) {
compare[s.charAt(i)]++;
}
for (int i = 0; i < s.length(); i++) {
if (compare[s.charAt(i)] == 1) return s.charAt(i);
}
return ' ';
}
}
class Solution {
public int reversePairs(int[] nums) {
return mergeSort(nums, 0, nums.length - 1);
}
private int mergeSort(int[] nums, int left, int right) {
if (left >= right) return 0;
int mid = (left + right) >> 1;
return mergeSort(nums, left, mid) + mergeSort(nums, mid + 1, right) + merge(nums, left, mid, right);
}
private int merge(int[] nums, int left, int mid, int right) {
int i = left;
int j = mid + 1;
int k = 0;
int count = 0;
int res[] = new int[right - left + 1];
while (i <= mid && j <= right) {
if (nums[i] > nums[j]) count += mid - i + 1;//如果j位置小于i位置,那么j位置小于i位置后所有的左半边的数
res[k++] = nums[i] <= nums[j] ? nums[i++] : nums[j++];
}
while (i <= mid) res[k++] = nums[i++];
while (j <= right) res[k++] = nums[j++];
for (int l = 0; l < res.length; l++) {
nums[left + l] = res[l];
}
return count;
}
}
两条路径交替遍历两条链表,同时遍历完成的节点就是交点
如果两个链表没有交点,那么会在第二次互换链表头时相同,node1==node2==null
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode node1 = headA;
ListNode node2 = headB;
while (node1 != node2) {
node1 = node1 == null ? headB : node1.next;
node2 = node2 == null ? headA : node2.next;
}
return node1;
}
}
class Solution {
public int search(int[] nums, int target) {
if (nums == null || nums.length == 0) return 0;
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = (left + right) >> 1;
if (nums[mid] == target) {
int i = mid - 1;
int j = mid + 1;
while (i >= 0 && nums[i] == target) i--;
while (j < nums.length && nums[j] == target) j++;
return j - i - 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return 0;
}
}
异或的内容除去待查找值都出现了两次,剩下的就是待查找的值
(双100%)
class Solution {
public int missingNumber(int[] nums) {
int len = nums.length;
int res = 0;
for (int i = 0; i < len; i++) {
res ^= i ^ nums[i];
}
return res ^ len;
}
}
利用二叉搜索树中序遍历是递增的特点,将left
和right
的遍历位置交换后就是递减的形式,然后计数第几个就是第几大了
class Solution {
private int count = 0;
private int res;
public int kthLargest(TreeNode root, int k) {
search(root, k);
return res;
}
private void search(TreeNode root, int k) {
if (root == null) return;
search(root.right, k);
count++;
if (count == k)
res = root.val;
search(root.left, k);
}
}
class Solution {
public int maxDepth(TreeNode root) {
return root == null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
}
class Solution {
int flag = 0;
public boolean isBalanced(TreeNode root) {
helper(root);
return flag == 0;
}
private int helper(TreeNode root) {
if (root == null) return 0;
int left = helper(root.left);
int right = helper(root.right);
if (Math.abs(left - right) > 1) flag = 1;
return 1 + Math.max(left, right);
}
}
当有两个只出现一次的数时,通过一遍异或运算不能得出最终值,但是可以得到这两个只出现一次的值的异或值
获取异或值的最尾部的1的位置,以该位置有无1将整个数组的数分成两部分
每部分就只有一个数出现一次了,就可以一次遍历异或找出
(双100%)
class Solution {
public int[] singleNumbers(int[] nums) {
int tmp = 0;
int res[] = new int[2];
for (int num : nums) {
tmp ^= num;
}
int flag = tmp & -tmp;//确定1的位置
for (int num : nums) {
if ((num & flag) == 0) res[0] ^= num;
else res[1] ^= num;
}
return res;
}
}
每个数字都是32位的,分别统计每一位所有数的和,如果只出现一次的值某一位为0,则统计值%3==0
,如果只出现一次的值某一位为1,则统计值%3==1
,余数可以得出只出现一次的值
(50%,100%)更快的解法用状态机,O(32) 没有这个答案直观好理解,已经是除状态机最优了
class Solution {
public int singleNumber(int[] nums) {
int res = 0;
for (int i = 0; i < 32; i++) {
int count = 0;
for (int n : nums) {
if ((n & (1 << i)) != 0) count++;
}
if (count % 3 == 1) res |= (1 << i);
}
return res;
}
}
因为数据是有序的,所以使用双指针,从两端逼近
(99%,100%)
class Solution {
public int[] twoSum(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
int count = nums[left] + nums[right];
if (count == target)
return new int[]{nums[left], nums[right]};
else if (count > target)
right--;
else
left++;
}
return new int[0];
}
}
滑动窗口,如果和小于target,右窗扣右移,加上右一个的值,如果和大于target,左窗口右移,减去之前左窗口的值没如果和等于target,将窗口的值存入数组
(73%,100%)
class Solution {
public int[][] findContinuousSequence(int target) {
List<int[]> res = new ArrayList<>();
int left = 1;
int right = 1;
int sum = 0;
while (left <= target / 2) {
if (sum < target) {
sum += right++;
} else if (sum > target) {
sum -= left++;
} else {
int arr[] = new int[right - left];
for (int i = left; i < right; i++) {
arr[i - left] = i;
}
res.add(arr);
sum -= left++;
}
}
return res.toArray(new int[res.size()][]);
}
}
用split以空格将字符串分割,然后倒序拼接,需要判断分割的结果是不是空串
(99%,100%)
class Solution {
public String reverseWords(String s) {
if (s.trim().equals("")) return "";
String[] s1 = s.trim().split(" ");
StringBuilder res = new StringBuilder();
for (int i = s1.length - 1; i >= 0; i--) {
if (!s1[i].equals("")) res.append(s1[i] + " ");
}
return new String(res.substring(0, res.length() - 1));
}
}
分割时可使用正则表达式去除多个空串的情况,但是效率较低
(24%,100%)
class Solution {
public String reverseWords(String s) {
String[] s1 = s.trim().split(" +");
StringBuilder res = new StringBuilder();
for (int i = s1.length - 1; i >= 0; i--) {
res.append(s1[i] + " ");
}
return new String(res.substring(0, res.length() - 1));
}
}
class Solution {
public String reverseLeftWords(String s, int n) {
char[] arr = s.toCharArray();
reserve(arr, 0, arr.length - 1);
reserve(arr, 0, arr.length - n - 1);
reserve(arr, arr.length - n, arr.length - 1);
return new String(arr);
}
private void reserve(char[] arr, int start, int end) {
while (start < end) {
char tmp = arr[start];
arr[start] = arr[end];
arr[end] = tmp;
start++;
end--;
}
}
}
字符串切割后拼接
(双100%)
class Solution {
public String reverseLeftWords(String s, int n) {
return s.substring(n, s.length()) + s.substring(0, n);
}
}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0) return new int[0];
//定下双端队列
LinkedList<Integer> queue = new LinkedList<>();
//定义结果集
int[] res = new int[nums.length - k + 1];
//遍历数组
for (int i = 0; i < nums.length; i++) {
//判断当前队列中队首元素是否有效
if (!queue.isEmpty() && queue.peekFirst() <= i - k) {
queue.poll();
}
//保证队列是由大到小的,所以如果队尾元素小于num[i],需要弹出
while (!queue.isEmpty() && nums[queue.peekLast()] <= nums[i]) {
queue.pollLast();
}
//添加当前值对应数组下标
queue.addLast(i);
//当窗口长度为K时,保存当前窗口最大值
if (i + 1 >= k) {
res[i + 1 - k] = nums[queue.peekFirst()];
}
}
return res;
}
}
class MaxQueue {
private Deque<Integer> queue;
private Deque<Integer> help;
public MaxQueue() {
queue = new LinkedList<>();
help = new LinkedList<>();
}
public int max_value() {
return queue.isEmpty() ? -1 : help.peekFirst();
}
public void push_back(int value) {
queue.offerLast(value);
//维护单调队列
while (!help.isEmpty() && value > help.peekLast()) {
help.pollLast();
}
help.offerLast(value);
}
public int pop_front() {
if (queue.isEmpty()) return -1;
int val = queue.pollFirst();
//维护单调队列
if (help.peekFirst() == val) {
help.pollFirst();
}
return val;
}
}
找状态转移方程也就是找各个阶段之间的转化关系,同样我们还是只需分析最后一个阶段,分析它的状态是如何得到的。
最后一个阶段也就是投掷完 n 枚骰子后的这个阶段,我们用 dp[n][j]
来表示最后一个阶段点数 j 出现的次数。
单看第 n 枚骰子,它的点数可能为 1 , 2, 3, … , 6,因此投掷完 n 枚骰子后点数 j 出现的次数,可以由投掷完 n-1 枚骰子后,对应点数 j-1, j-2, j-3, … , j-6 出现的次数之和转化过来。
for (第n枚骰子的点数 i = 1; i <= 6; i ++) {
dp[n][j] += dp[n-1][j - i]
}
总的次数为6^n次,n次投骰子产生的结果最多5*n+1种,最后用dp统计的次数除总数得概率
class Solution {
public double[] twoSum(int n) {
int[][] dp = new int[n + 1][6 * n + 1];
//初始化
for (int j = 1; j <= 6; j++) {
dp[1][j] = 1;
}
for (int i = 2; i <= n; i++) {
for (int j = i; j <= i * 6; j++) {
for (int cur = 1; cur <= 6; cur++) {
if (j - cur <= 0) break;
dp[i][j] += dp[i - 1][j - cur];
}
}
}
//计算总数
int all = (int) Math.pow(6, n);
//n个骰子只有5*n+1中结果
double[] res = new double[5 * n + 1];
//计算每个位置的概率
for (int i = n; i <= 6 * n; i++) {
res[i - n] = dp[n][i] * 1.0 / all;
}
return res;
}
}
nums[i+1]==num[i]
,则一定不是顺子nums[i+1]-num[i]》1
,则需要用0来补位最后判断0的个数非负则是顺子
class Solution {
public boolean isStraight(int[] nums) {
Arrays.sort(nums);
int count = 0;
//统计0个数,通过移动下标,第一个非0 的下标就是0的数目
while (count < nums.length && nums[count] == 0) {
count++;
}
for (int i = count; i < nums.length - 1; i++) {
if (nums[i + 1] == nums[i]) return false;//两种非0牌相同,一定不是顺子
if (nums[i + 1] - nums[i] > 1) {//如果差>1,用0来补位
count -= nums[i + 1] - nums[i] - 1;
}
}
//剩余为0的牌大于等于0
return count >= 0;
}
}
class Solution {
public int lastRemaining(int n, int m) {
int last = 0;
for (int i = 2; i <= n; i++) {
last = (last + m) % i;
}
return last;
}
}
买卖股票一共有6个题,系统复习跳转动态规划的文章去看吧动态规划专栏
以购买的三种状态dp(5ms)
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length < 2) return 0;
int dp[][] = new int[prices.length][3];
dp[0][0] = 0;//没交易
dp[0][1] = -prices[0];//买入
dp[0][2] = 0;//卖出了
for (int i = 1; i < prices.length; i++) {
dp[i][0] = dp[i - 1][0];
dp[i][1] = Math.max(dp[i - 1][0] - prices[i], dp[i - 1][1]);
dp[i][2] = Math.max(dp[i - 1][1] + prices[i], dp[i - 1][2]);
}
return dp[prices.length - 1][2];
}
}
以是否持有dp(6ms)
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length < 2) return 0;
int dp[][] = new int[prices.length][2];
dp[0][0] = 0;//不持有
dp[0][1] = -prices[0];//持有
for (int i = 1; i < prices.length; i++) {
dp[i][0] = Math.max(dp[i - 1][1] + prices[i], dp[i - 1][0]);
dp[i][1] = Math.max(- prices[i], dp[i - 1][1]);
}
return dp[prices.length - 1][0];
}
}
贪心(1ms)
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length < 2) return 0;
int min = prices[0];
int resMax = 0;
for (int i = 1; i < prices.length; i++) {
if (prices[i] < min) min = prices[i];
else {
resMax = Math.max(resMax, prices[i] - min);
}
}
return resMax;
}
}
如果没有限制,那就是一个递归,递归终止条件时if n==0
没有if这些做判断终止条件,可以使用短路与
或者短路或
实现
短路与:如果n<=0
,此时(n > 0) && ((n += sumNums(n - 1)) > 0);
执行到(n > 0) ==fasle
就不会执行之后的数据了,然后开始返回
class Solution {
public int sumNums(int n) {
boolean b = (n > 0) && ((n += sumNums(n - 1)) > 0);
return n;
}
}
短路或,同理
class Solution {
public int sumNums(int n) {
boolean b = (n <= 0) || ((n += sumNums(n - 1)) > 0);
return n;
}
}
a^b
(a&b)<<1
不进位加法a^b
进位数计算(a&b)<<1
举例:计算12+7
12:1100
7:0111
12^7 = 1011
(12&7)<<1 = 1000
第一轮循环得到以上两个结果,递归调用,求1011+1000
1011^1000 = 0011
(1011&1000)<<1 = 10011
第二轮循环得以上两个结果,递归调用,求0011+10011
00011^10011 = 10000
(00011&10011)<<1 = 00011
第二轮循环得以上两个结果,递归调用,求10000+00011
10000^00011 = 10011
(10000&00011)<<1 = 0
return 10011 获得结果
迭代:
public class Solution {
public int getSum(int a, int b) {
while (b != 0) {
//不进位加法
int tmp = a ^ b;
//计算进位值
int carry = (a & b) << 1;
a = tmp;
b = carry;
}
return a;
}
}
递归
class Solution {
public int add(int a, int b) {
//同位a^b
//进位a&b<<1
return b == 0 ? a : add(a ^ b, (a & b) << 1);
}
}
120 = 2*3*4*5;
60 = 1*3*4*5;
40 = 1*2*4*5;
30 = 1*2*3*5;
24 = 1*2*3*4;
定义一个数组b存放结果,先一次遍历,从1位置开始,存入i位置左边的所有数的积
反过来从n-2位置开始,累积i右边所有数的积
class Solution {
public int[] constructArr(int[] a) {
if (a == null || a.length == 0) return a;
int n = a.length;
int[] b = new int[n];
b[0] = 1;//初始化第一个为1
//计算i位置左边的数之积
int tmp = 1;
for (int i = 1; i < n; i++) {
tmp *= a[i - 1];
b[i] = tmp;
}
//计算i位置右边的数之积
tmp = 1;
for (int i = n - 2; i >= 0; i--) {
tmp *= a[i + 1];
b[i] *= tmp;
}
return b;
}
}
用数组暂存
class Solution {
public int strToInt(String str) {
int index = 0;//下标
int flag = 0;//表示正负数 0表示正数,1表示负数
int len = str.length();//str长度
//去空格
while (index < len && str.charAt(index) == ' ') index++;
//去符号
if (index < len && (str.charAt(index) == '+' || str.charAt(index) == '-')) {
if (str.charAt(index) == '-') flag = 1;
index++;
}
//去首部0
while (index < len && str.charAt(index) == '0') index++;
//判断如果非数字就返回了
if(index < len && str.charAt(index) < '1' && str.charAt(index) > '9') return 0;
//数组替代双端队列
int[] nums = new int[10];
int nums_index = 0;
//添加入队列
while (index < len && str.charAt(index) >= '0' && str.charAt(index) <= '9') {
if (nums_index == 10 || nums_index == 9 && nums[0] > 2) {
return flag == 0 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
nums[nums_index++] = str.charAt(index++) - '0';
}
int res = 0;
//实现数的重组
for (int i = nums_index - 1; i >= 0; i--) {
Integer cur = nums[i];
//每一位转化为对应整数
cur *= (int) Math.pow(10, nums_index - 1 - i);
res += cur;
if (flag == 0 && res < 0) {
return Integer.MAX_VALUE;
} else if (cur != 0 && flag == 1 && res - 1 < 0) {
return Integer.MIN_VALUE;
}
}
return flag == 0 ? res : -res;
}
}
用双端队列暂存
class Solution {
public int strToInt(String str) {
int index = 0;//下标
int flag = 0;//表示正负数 0表示正数,1表示负数
int len = str.length();//str长度
//去空格
while (index < len && str.charAt(index) == ' ') index++;
//去符号
if (index < len && (str.charAt(index) == '+' || str.charAt(index) == '-')) {
if (str.charAt(index) == '-') flag = 1;
index++;
}
//去首部0
while (index < len && str.charAt(index) == '0') index++;
//判断如果非数字就返回了
while (index < len && str.charAt(index) < '1' && str.charAt(index) > '9') return 0;
//用双端队列暂存数字,方便转数,并且方便判断最高位值,不满足可以提前退出
Deque<Integer> queue = new LinkedList<>();
//添加入队列
while (index < len && str.charAt(index) >= '0' && str.charAt(index) <= '9') {
queue.push(str.charAt(index++) - '0');
}
//不符合条件的情况1(如果位数超过10位或者10位的第一位大于2,则已经越界了,按越界规则返回)
if (queue.size() > 10 || queue.size() == 10 && queue.peekLast() > 2) {
return flag == 0 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
int res = 0;
int size = queue.size();
//实现数的重组
for (int i = 0; i < size; i++) {
Integer cur = queue.pop();
//每一位转化为对应整数
cur *= (int) Math.pow(10, i);
res += cur;
if (flag == 0 && res < 0) {
return Integer.MAX_VALUE;
} else if (cur != 0 && flag == 1 && res - 1 < 0) {
return Integer.MIN_VALUE;
}
}
return flag == 0 ? res : -res;
}
}
二叉搜索树满足左子树的所有节点值小于根节点值,右子树所有节点值大于根节点值
所以可以将p,q的值分成三类来递归
(双100%)
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q);
else if (root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q);
return root;
}
}
递归查找,如果找到节点或者下探到空节点开始返回
如果左右子树都不为空,找到祖先节点
(双100%)
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//递归终止条件,下探发现目标节点或者到达空节点了开始出栈
if (root == null || root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
//如果左右两个节点都不为空,说明root节点就是祖先节点
if (left != null && right != null) return root;
//当前节点不是祖先节点,返回查找结果
return left == null ? right : left;
}
}
不带剪枝的递归,思路更加简单,统计每颗子树符合的节点有多少个 ,满足就暂存
(30%,100%)
class Solution {
TreeNode res;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
helper(root, p, q);
return res;
}
//寻找子树有几个节点符合
private int helper(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) return 0;
int left = helper(root.left, p, q);
int right = helper(root.right, p, q);
int cur = left + right;
if (root == p || root == q) cur++;//如果当前节点满足,计数+1
//如果找到了祖先节点,暂存,并将计数器置为-1
if (cur == 2) {
res = root;
cur = -1;
}
return cur;
}
}