本文中的部分图片摘自相关题解榜主,如有侵权,请联系删除。
特别感谢k神在剑指Offer刷题路上提供的清晰图解、和堪称完美的思路与方法
把数放到该放的位置去
class Solution {
public int findRepeatNumber(int[] nums) {
int len = nums.length;
if(len == 0){return -1;}
for(int i =0;i<nums.length;i++){
if(nums[i]<0 || nums[i]>=len){
return -1;
}
while(nums[i] != i){
if(nums[i] == nums[nums[i]]){return nums[i];}
int temp = nums[i];
nums[i] = nums[temp];
nums[temp] = temp;
}
}
return -1;
}
}
对于一个整数范围L~ R,如果在这个范围内的整数的数量超过R-L+1,那么此范围内的整数中必然有整数会出现多次【重复】。因此选定范围1-n/2,并记录输入数组中在此范围内的元素的数量,若数量大于n/2,则在此整数范围内必然有整数在数组中出现了多次,否则在范围(n/2)+1~n内必然有整数在数组中出现多次。对确定出现重复整数的范围再进行前面的划分和【计数】操作,直到找到重复的数字。
代码:
class Solution {
//l和r只是表示数的范围,并不是数组的指针和下标,每次用于统计在这个范围内的数量
public int duplicateInArray(int[] nums) {
int l = 1;//数字范围是1~n
int r = nums.length-1;
while(l<r){
int count = 0;
int mid = l + r >> 1;
for(int num : nums){//统计在左半边[1,l]数的个数
if(num<=mid && num>=l) count++;
}
if(count>mid-l+1) r=mid;//左半边重复
else l = mid+1;//右半边重复
}
return r;
}
}
-牛客上返回ArrayList:用栈的先进后出思想
class Solution {
public HashMap<Integer,Integer> IndexMap;
public TreeNode buildTree(int[] preorder, int[] inorder) {
int len = preorder.length;
IndexMap = new HashMap<>();
for(int i=0;i<len;i++){
IndexMap.put(inorder[i],i);
}
return mybuildTree(preorder,inorder,0,len-1,0,len-1);
}
public TreeNode mybuildTree(int[] preorder,int[] inorder,int preorder_left,int preorder_right,int inorder_left,int inorder_right){//前序左边界、右边界、中序左边界、右边界
if(preorder_left>preorder_right){
return null;//出界,表示此树已经遍历完
}
int pre_root_index = preorder_left;//当前 前序根节点位置,即第一个节点
int in_root_index = IndexMap.get(preorder[pre_root_index]);//根据上面找到的根节点查找其在当前中序中的位置
TreeNode root = new TreeNode(preorder[pre_root_index]);//定义当前根节点
int len_in_left = in_root_index-inorder_left; //在中序中找到当前左子树的结点个数
// 递归地构造左子树,并连接到根节点
// 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
root.left = mybuildTree(preorder,inorder,preorder_left+1,preorder_left+len_in_left,inorder_left,in_root_index-1);
// 递归地构造右子树,并连接到根节点
// 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
root.right = mybuildTree(preorder,inorder,preorder_left+len_in_left+1,preorder_right,in_root_index+1,inorder_right);
return root;
}
}
这里的pNode.next 指的是指向父节点的指针,而不是下一个节点
class CQueue {
private Stack<Integer> stackPush;//加入只从stackPush栈加入
private Stack<Integer> stackPop;//删除只从stackPop栈弹出
public CQueue() {
stackPush = new Stack<>();
stackPop = new Stack<>();
}
public void appendTail(int value) {
stackPush.push(value);
pushTopop();//只有pop栈为空才执行
}
public int deleteHead() {
if(stackPop.isEmpty() && stackPush.isEmpty()){
return -1;
}
pushTopop();
return stackPop.pop();
}
public void pushTopop(){
if(stackPop.isEmpty()){//只有pop栈为空才执行
while(!stackPush.isEmpty()){//要倒就全倒过去
stackPop.push(stackPush.pop());
}
}
}
}
public class Solution {
public int rectCover(int n) {
int fib_two = 1;
int fib_one = 2;
int fib_N = 0;
if(n<=2){
return n;
}
for(int i = 3;i<=n;i++){
fib_N = fib_one+fib_two;
fib_two = fib_one;
fib_one = fib_N;
}
return fib_N;
}
}
class Solution {
public int findMin(int[] arr) {
int start = 0;
int end = arr.length-1;
if(arr == null || arr.length == 0) return -1;
if(arr.length == 1) return arr[0];
while(start < end){
int mid = start + (end-start)/2;
if(arr[mid] > arr[end]){
//大于arr[end]说明arr[mid]必定不是最小值 ,因为arr[end]左边的值更小,所以mid+1
start = mid +1;
}else if(arr[mid] < arr[end]){
end = mid;//小于arr[end]说明 arr[mid]可能是最小值 ,所以mid不减1
}else{//arr[end] = arr[mid]
end--;//若arr[mid]在最小值右边,则end左移即可
//若arr[mid]在最小值左边,end左移即可
}
}
return arr[start];
}
}
class Solution {
public boolean exist(char[][] arr, String word) {
char[] words = word.toCharArray();
if(arr == null || arr.length == 0){
return false;
}
for(int i=0;i<arr.length;i++){
for(int j=0;j<arr[0].length;j++){
if(hasPath(arr,words,i, j, 0)) return true;//从words第0个元素开始
}
}
return false;
}
public boolean hasPath(char[][] arr, char[] words, int i, int j, int word_k){
if(i>=arr.length || i<0 ||j<0 ||j>=arr[0].length || arr[i][j]!=words[word_k]) {
return false;
//1)行或列越界2)当前矩阵元素与目标字符不同3)当前元素已访问过,其中(3)可合并至(2),因为访问过即为'\0',必!=words[word_k]
}
if(word_k == words.length-1) return true;//到达字符串末尾,说明都找到了
arr[i][j] = '\0';
//朝当前元素的 上、下、左、右 四个方向开启下层递归
boolean res = hasPath(arr,words,i+1,j,word_k+1) || hasPath(arr,words,i-1,j,word_k+1)
|| hasPath(arr,words,i,j+1,word_k+1) || hasPath(arr,words,i,j-1,word_k+1);
arr[i][j] = words[word_k]; //当一直搜索至字符串最后一个元素后,开始回溯返回,依次把置空的元素恢复
return res;//返回布尔量 res ,代表是否搜索到目标字符串
}
}
class Solution {
int m,n,k;
boolean[][] isVisted;
public int movingCount(int m, int n, int k) {
this.m = m; this.n = n; this.k = k;
isVisted = new boolean[m][n];
int count = isGoTo(0,0,0,0);//从(0,0)位置开始搜索
return count;
}
public int isGoTo(int i,int j,int sum_i,int sum_j){
if(i>=m || j>=n ||sum_i+sum_j>k || isVisted[i][j] == true){
return 0;
}
isVisted[i][j] = true;
return 1 + isGoTo(i+1,j,sum(i+1),sum_j) + isGoTo(i,j+1,sum_i,sum(j+1));
//因为从(0,0)开始,走的方向只有向下和向上
//返回 当前格子 + 下搜索的个数 + 右搜索的个数
}
public int sum(int x){
int s =0;
while(x!=0){
s += x%10;
x = x/10;
}
return s;
}
}
class Solution {
//有价值的因子只有2和3,因为4 = 2+2=2*2分不分都一样了,而5以后的数都需要进一步做分解才更优。而且同样的n,分出3比分出2更优(比如3*3大于2*2*2),所以尽可能分出更多的3就是解法,当分出若干3后,n≤4时,此时n若为2,为3,为4,直接乘就都是最优解了
//所有绳子的长度相等时,乘积最大 2、最优绳长为3,先按3分段,即n=3*a+b,则b可能=0,1,2.
//b=0则直接返回3^a取余; b=1,将一个1+3换成2+2,即返回(3^(a-1)*4)取余; b=2,则返回(3^a*2)取余
public int cuttingRope(int n) {
if(n<2) return 0;
if(n == 2) return 1;
if(n == 3) return 2;
long res = 1;
while(n>4){//n=4 :分为2*2,即res*2*2 = res*4 = res*n对应b=1的情况
res *= 3;
res %= 1000000007;
n -= 3;
}//出来循环有三种情况,分别是n=2、3、4,分别对应b=2、b=0、b=1的情况
return (int)(res * n % 1000000007);
}
}
class Solution {
// 不考虑大数
// public int[] printNumbers(int n) {
// int end = (int)Math.pow(10,n)-1;
// int[] res = new int[end];
// for(int i=0;i
// res[i] = i+1;
// }
// return res;
// }
private List<Integer> list;//list用来存每一次的数
public int[] printNumbers(int n) {
list = new ArrayList();
dfs(n,0,new StringBuilder());//从0开始递归
int[] res = new int[list.size()];
for(int i=0;i<res.length;i++){
res[i] = list.get(i);//res 将list变为数组,因为题目要求输出数组
}
return res;
}
public void dfs(int n,int n_index,StringBuilder num){//n_index 用来计数当前到达的位数
if( n_index == n ){//递归结束条件
//当到达指定位数,结束当前,开始回溯。如:到达000000001后回溯到000000010;到达000018889后,回溯到000019000
while(num.length() != 0 && num.charAt(0) == '0'){//num 用来存放当前的这个数,即只有一个
num.deleteCharAt(0);//字符串将左边多余的0删除;num.length() != 0:保证至少留下最后一个0
}
// 将字符串形式的'数',转化为整数
if(num.length() != 0){
list.add(Integer.valueOf(num.toString()));//list 存放每一个数,累积存
}
return;
}
for(int j=0;j<=9;j++){
num.append(j);
dfs(n,n_index+1,num);//向下深度一遍,n_index计数+1
if(num.length() != 0){
num.deleteCharAt(num.length()-1);
//回溯时,把上一次的最后一个数删除,否则会加入下一次中
//否则输入 1,正确结果是[1,2,3,4,5,6,7,8,9],
//不删的话是[1,12,123,1234,12345,123456,1234567,12345678,123456789]
}
}
}
}
class Solution {
public boolean isMatch(String s, String p) {
int slen = s.length();
int plen = p.length();
boolean[][] dp = new boolean[slen+1][plen+1];
for(int i=0;i<=slen;i++){//[0,slen]
for(int j =0;j<=plen;j++){//[0,plen]
//分成1、空正则和 2、非空正则两种
if(j==0){//1、空正则
dp[i][j] = i==0 ;//只有i=0且j=0 才匹配,其他都为false
}else{// 2、非空正则
//非空正则分为两种情况 2.1、当前正则串字符是非* 和 2.2、是*
if(p.charAt(j-1) != '*' ){ // 2.1、非*
if(i>0 && (s.charAt(i-1) == p.charAt(j-1) || p.charAt(j-1) == '.')){
dp[i][j] = dp[i-1][j-1];
}
}else{// 2.2、*
//碰到 * 了,分为看和不看两种情况
if(j>=2){//不看,即当前s字符不等于p*字符前的那个字符,直接砍掉p的*和*前字符
dp[i][j] = dp[i][j-2];
}
//看,即当前s的字符等于p*字符前的那个字符
if(i>=1 && j>=2 &&
(s.charAt(i-1) == p.charAt(j-2) || p.charAt(j-2) == '.')){
dp[i][j] |= dp[i-1][j];//注意或非:考虑到上面的情况,或上不看的结果
}
}
}
}
}
return dp[slen][plen];
}
}
class Solution {
public boolean isNumber(String s) {
boolean hasNum= false,hasDot= false,hasE= false,hasSign = false;
int index=0;
int n = s.length();
while(index<n && s.charAt(index) == ' ') index++;
while(index<n){
while(index<n && s.charAt(index)>='0' && s.charAt(index)<='9'){
index++;
hasNum = true;
}
if(index == n) break;
char c = s.charAt(index);
if(c == 'e' || c =='E' ){
if(hasE || !hasNum) return false;
hasE = true;
hasNum = false;hasDot = false;hasSign = false;
//开始遍历e后的新数字,其他状态都清空。
//如:0e,hasNum不清空的话,会输出true。 但 0e ×
}else if(c == '+' || c == '-'){
if(hasNum || hasSign || hasDot) return false;
hasSign = true;
}else if(c == '.'){
if(hasDot || hasE) return false;//.前没数字也可以。如:.1 √
hasDot = true;
}else if(c == ' '){
break;//如果字符之间有空格 则跳出本次循环,使得最终的index不能和n相等
}else{//表示是其他非法字符
return false;
}
index++;
}
while(index<n && s.charAt(index) == ' '){
index++;
}
return hasNum && index == n;
}
}
-添加边界:k大于链表长度
class Solution {//递归
public TreeNode mirrorTree(TreeNode root) {
if(root == null) return null;
TreeNode tmp = root.left;
root.left = mirrorTree(root.right);
root.right = mirrorTree(tmp);
return root;
}
}
2、辅助栈:先交换左子树的左右节点,然后再交换右子树的左右节点
class Solution {//辅助栈
public TreeNode mirrorTree(TreeNode root) {
if(root == null) return null;
Stack<TreeNode> stack = new Stack<>();
stack.add(root);//先加入根节点
while(!stack.isEmpty()){
TreeNode node = stack.pop();
//当前节点出栈(理论上是上一轮中后入栈的右节点,但上一轮又交换了左右节点,所以出栈的是上一轮左节点
if(node.left != null) stack.add(node.left);//先加入左节点
if(node.right != null) stack.add(node.right);//次加入右节点
TreeNode tmp = node.left;//交换两节点
node.left = node.right;
node.right = tmp;
}
return root;
}
}
题目:
同LeetCodeT54:螺旋矩阵
方法
1)、把矩阵按照一圈一圈处理,先顺时针输出最外圈。初始化最外圈左上角和右下角元素坐标。
2)、向内缩减一圈(左上和右下元素坐标向内移动),再次顺时针遍历
3)、直到最后只剩下一列或者一行,则输出该列或者行
代码:
class Solution {
public int[] spiralOrder(int[][] arr) {
if(arr.length == 0) return new int[0];
int Lr = 0;
int Lc = 0;
int Rr = arr.length-1;
int Rc = arr[0].length-1;
int[] res = new int[(Rr+1)*(Rc+1)];
int i = 0;
//顺时针遍历一圈
while(Lr<=Rr && Lc<=Rc){
int cur_r = Lr;
int cur_c = Lc;
if(Lr == Rr){//如果只剩下一行
res[i++] = arr[Lr][Lc++];
}else if(Lc == Rc){//如果只剩下一列
res[i++] = arr[Lr++][Lc];
}else{
while(cur_c<Rc){//先从左往右
res[i++] = arr[cur_r][cur_c++];
}
while(cur_r<Rr){//再从上至下
res[i++] = arr[cur_r++][cur_c];
}
while(cur_c>Lc){//再从右向左
res[i++] = arr[cur_r][cur_c--];
}
while(cur_r>Lr){//再从下至上
res[i++] = arr[cur_r--][cur_c];
}
//整体向内移动一圈
Lr++;
Lc++;
Rr--;
Rc--;
}
}
return res;
}
}
题目:
同LeetCodeT155:最小栈:定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
方法
创建两个栈,一个存数据,一个存数据的最小值。
class MinStack {
private Stack<Integer> stackData;
private Stack<Integer> stackMin;
/** initialize your data structure here. */
public MinStack() {
this.stackData = new Stack();
this.stackMin = new Stack();
}
public void push(int x) {
stackData.push(x);
if(stackMin.isEmpty() || x< stackMin.peek()){
stackMin.push(x);
}else{
stackMin.push(stackMin.peek());// 如果输入的数大于当前辅助栈顶,则辅助栈压入上一次压入的最小值(栈顶)
}
}
public void pop() {
if(!stackData.isEmpty()){
stackData.pop();
stackMin.pop();
// 因为数据栈和辅助栈同时入栈、出栈,故长度保持一致,你空它也空
}
}
public int top() {
return stackData.peek();
}
public int min() {
return stackMin.peek();
}
}
如图,在辅助栈先按照给的压栈顺序压入数字1、2、3、4,当压入的数字4和给定的弹出第一个数字4相同时,说明该弹出了,于是从辅助栈中弹出这个相同的数4,然后指向弹出顺序的i后移,指向5,接着按照压入栈顺序压入5,发现和i指向的数相同,说明该弹出来,于是从辅助栈弹出5,i后移,此时stack栈顶是3,和i指向相同,弹出3,i后移指向2,stack此时 栈顶是2,一样则弹出2…弹出1,stack为空,模拟成功
代码:
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
int n = pushed.length;
int m = popped.length;
if(m != n) return false;
Stack<Integer> stack = new Stack();
int j =0;
for(int i =0;i<n;i++){
stack.push(pushed[i]);//先压入第一个数
while(!stack.isEmpty() && stack.peek() == popped[j]){
//辅助栈栈顶数 == 指向的弹出数,说明该模拟弹出了
stack.pop();
j++;//指向弹出顺序栈的索引后移
}
}
return stack.isEmpty();
}
}
class Solution {//BFS--->队列
public int[] levelOrder(TreeNode root) {
if(root == null) return new int[0];
Queue<TreeNode> queue = new LinkedList<>(){{add(root);}};
ArrayList<Integer> res = new ArrayList<>();
while(!queue.isEmpty()){//队列空跳出循环
TreeNode node = queue.poll();//弹出当前数
res.add(node.val);
if(node.left != null) {//加入当前数的左节点
queue.add(node.left);
}
if(node.right != null){//加入当前数的右节点
queue.add(node.right);
}
}
int[] res_arr = new int[res.size()];
for(int i=0;i<res_arr.length;i++){
res_arr[i] = res.get(i);
}
return res_arr;
}
}
class Solution {
public boolean verifyPostorder(int[] postorder) {
return recur(postorder,0,postorder.length-1);
}
public boolean recur(int[] arr, int i , int j){
if(i>=j){
return true;
}
int temp = i;
while(arr[temp]<arr[j]){// 找左子树
temp++;
}
int m = temp;// m表示当前右子树的第一个节点
while(arr[temp]>arr[j]){//找右子树
temp++;
}
return temp == j && recur(arr, i, m-1) && recur(arr,m,j-1);
}
}
class Solution {
private LinkedList<List<Integer>> res = new LinkedList<>();//记录符合要求的路径集合
private LinkedList<Integer> path = new LinkedList<>();//记录当前的路径
public List<List<Integer>> pathSum(TreeNode root, int tar) {
recur(root,tar);
return res;
}
public void recur(TreeNode root ,int tar){
if(root == null) return;//回溯条件
path.add(root.val);
tar -= root.val;
if(tar == 0 && root.left == null && root.right == null){
//如果tar=0,且到达叶节点,才说明符合要求
res.add(new LinkedList(path));
// res.add(path)是将path对象加入了res后续path改变时,res中的path对象也变了
}
recur(root.left,tar);
recur(root.right,tar);
path.removeLast();//回溯时,删除当前节点值
}
}
和普通链表区别: 普通链表一般只有next指针,此链表多了一个指向随机的指针random;因此不能像一般的一个节点一个节点的复制指、next,因为random指向的数在复制时可能还没定义,比如第二个node的random指向第7个node,此时第7个node还未被定义
过程:
1、构建一新链表,用哈希表的dic存原表和新链表对应键值关系,哈希表存的是每一个节点,即包括每个节点的next和random指向
2、构建新链表的引用指向
代码:
class Solution {
public Node copyRandomList(Node head) {
Node cur = head;
if(head == null) return null;
// 复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
HashMap<Node,Node> dis = new HashMap();
while(cur != null){
dis.put(cur,new Node(cur.val));
cur = cur.next;
}
//构建新节点的 next 和 random 指向
cur = head;
while(cur != null){
//将原链表每个节点的next、random指向,添加到哈希表中对应的这个节点上
dis.get(cur).next = dis.get(cur.next);
dis.get(cur).random = dis.get(cur.random);
cur = cur.next;
}
//返回新链表的头节点
return dis.get(head);
}
}
题目:
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
思路:
实则为每一个节点重新分配.left、.right指向,实现循环链表的表示
class Solution {
Node pre;
Node head;
public Node treeToDoublyList(Node root) {
if(root == null) return null;
dfs(root);
//建立首尾指向,pre此时指向链表最后一个元素
head.left = pre;
pre.right = head;
return head;
}
public void dfs(Node cur){
if(cur == null) return;//递归中止条件
//开始中序遍历
dfs(cur.left);
if(pre!=null){
pre.right = cur;
}else head = cur;
//pre = null,说明此时cur是链表第一个节点,因为 空.right ×
cur.left = pre;
pre = cur;//pre后移
dfs(cur.right);
}
}
同LeetCodeT297
反序列化:队列层序遍历BFS
public class Codec {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {//层序遍历+队列
if(root == null) return "[]";
StringBuilder res = new StringBuilder("[");
Queue<TreeNode> queue = new LinkedList<>(){{add(root);}};
while(!queue.isEmpty()){//以队列的形式进行层序遍历
TreeNode node = queue.poll();//当前节点出队列,下面准备加入该节点的左右节点
if(node != null){
res.append(node.val + ",");
queue.add(node.left);
queue.add(node.right);
}else res.append("null,");//如果为空则用null表示
}
res.deleteCharAt(res.length()-1);//删除最后一个“,”
res.append("]");
return res.toString();
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
if(data.equals("[]")) return null;
String[] vals = data.substring(1,data.length()-1).split(",");//去掉首尾[],以,分割
TreeNode root = new TreeNode(Integer.parseInt(vals[0]));
Queue<TreeNode> queue = new LinkedList<>(){{add(root);}};
int i = 1;//从vals第2个数开始,因为第一个数肯定是根节点,上面已经加到queue
while(!queue.isEmpty()){
TreeNode node = queue.poll();//当前节点出队列,下面准备加入该节点的左右节点
if(!vals[i].equals("null")){
node.left= new TreeNode(Integer.parseInt(vals[i]));//加入当前node左节点
queue.add(node.left);
}
i++;//i后移
if(!vals[i].equals("null")){
node.right= new TreeNode(Integer.parseInt(vals[i]));//加入当前node右节点
queue.add(node.right);
}
i++;
}
return root;//返回root
}
}
为什么要还原交换?
class Solution {
List<String> res = new LinkedList<>();
char[] c;
public String[] permutation(String s) {
c = s.toCharArray();
dfs(0);
return res.toArray(new String[res.size()]);
}
void dfs(int x){
HashSet<Character> set = new HashSet<>();
if(x == (c.length-1)){//一种排列方案已排好
res.add(String.valueOf(c));//res 添加这种方案
}
for(int i=x;i<c.length;i++){//交换当前为x 和x之后的每一位
if(set.contains(c[i])) continue;//包含,说明当前字符在该位置之前排过,则剪枝
set.add(c[i]);//不包含,则排,加入set
swap(i,x);//交换,将c[i]固定在第x位,
dfs(x+1);//开启固定第x+1 位字符
swap(x,i);//恢复交换
//返回时交换回来,这样保证到达第1层的时候,一直都是abc。
//这里捋顺一下,开始一直都是abc,那么第一位置总共就3个交换
//分别是a与a交换,这个就相当于 x = 0, i = 0;
// a与b交换 x = 0, i = 1;
// a与c交换 x = 0, i = 2;
//就相当于上图中一开始的三条路径
//第一个元素(eg. a)固定后,每个引出两条路径,
// b与b交换 x = 1, i = 1;
// b与c交换 x = 1, i = 2;
//所以,结合上图,在每条路径上标注上i的值,就会非常容易好理解了
}
}
void swap(int i,int j){
char tmp = c[i];
c[i] = c[j];
c[j] = tmp;
}
}
题目:
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
思路1:数组排序法
数组排序法:将数组 nums 排序,数组中点的元素 一定为众数
//1、数组排序法:排序后的数组的中点必定是“众数”,因为排序后,要想超过一半的数,则这个数的起点肯定落在0~中点的位置,且长度>=一半,则这个数覆盖的长度必定会跨过中点
public int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length/2];
}
摩尔投票法: 核心理念为 票数正负抵消 (主要是推论2)。
//推论:若数组的前a个数字票数和=0 ,则数组剩余 (n-a)个数字的票数和一定仍>0即后(n−a)个数字的众数仍是x
//遍历数组每一个数,假设当前这个数是众数,然后计票数+1,后面遇到和他相等的数,票数+1,遇到不等的数-1,直到票数=0,则当区间可去掉,因为后面区间的众数仍为x,则从下一个数开始循环上述操作。
public int majorityElement(int[] nums) {
int count = 0;//票数总和
int vote = 0;//投票数:+1、-1
int res = 0;//假设众数
for(int i=0;i<nums.length;i++){
if(count == 0 ) res =nums[i];//如果遇到票数和=0,从后面区间重新循环计数
vote = res == nums[i]? +1 : -1;//相等则投+1,否则-1
count += vote;
}
return res;
}
}
同LeetCodeT215: 数组中的第K个最大元素
题目:
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
思路:大根堆(父节点的值大于或等于左、右子节点的值)
用一个大根堆实时维护数组的前 k 小值。首先将前 k个数插入大根堆中,随后从第 k+1 个数开始,如果当前遍历到的数比大根堆的堆顶的数要小,就把堆顶的数弹出,再插入当前遍历到的数。最后将大根堆里的数存入数组返回即可。
代码:
class Solution {
//1、排序法去前k
// public int[] getLeastNumbers(int[] arr, int k) {
// Arrays.sort(arr);
// int[] res = new int[k];
// for(int i=0;i
// res[i] = arr[i];
// }
// return res;
// }
//2、大根堆法:利用java中现成的PriorityQueue
// 保持堆的大小为K,然后遍历数组中的数字,遍历的时候做如下判断:
// 1).若目前堆的大小小于K,将当前数字放入堆中。
// 2).否则判断当前数字与大根堆堆顶元素的大小关系,如果当前数字比大根堆堆顶大,这个数直接跳过;
// 反之如果当前数字比大根堆堆顶小,先poll掉堆顶,再将该数字放入堆中。
public int[] getLeastNumbers(int[] arr, int k) {
if(k == 0 || arr.length == 0) return new int[0];
// 默认是小根堆,实现大根堆需要重写一下比较器。
Queue<Integer> queue = new PriorityQueue<>((v1, v2) -> v2 - v1);
for(int i=0;i<arr.length;i++){
if(queue.size()<k){
queue.offer(arr[i]);
}else if(queue.peek()>arr[i]){//找小于 大根堆 堆顶的
queue.poll();//弹出堆顶(即大根堆最大值
queue.offer(arr[i]);//把这个数按照大根堆规则放入其中
}
}
//返回堆中元素
int[] res = new int[queue.size()];
for(int j=0;j<res.length;j++){
res[j] = queue.poll();
}
return res;
}
}
class Solution {
public int findKthLargest(int[] nums, int k) {
if(k == 0 || nums.length == 0) return -1;
Queue<Integer> queue = new PriorityQueue<>();
//用java现成的PriorityQueue,即小根堆,不用重写比较器
for(int i =0;i<nums.length;i++){
if(queue.size()<k){
queue.offer(nums[i]);
}else if(queue.peek()<nums[i]){//找比堆顶小的
queue.poll();
queue.offer(nums[i]);
}
}
return queue.poll();//返回小根堆堆顶
}
}
class MedianFinder {
//小根堆存放大的数,堆顶为最小的;大根堆存放小的数,堆顶为最大的;所以两个堆的堆顶数即是中位数位置
Queue<Integer> A;//小根堆,A.size() = m ; N=奇数时,m=n+1,即m>n; N=偶数,m=n
Queue<Integer> B;//大根堆, B.size() = n
/** initialize your data structure here. */
public MedianFinder() {
this.A = new PriorityQueue<>();//PriorityQueue默认小根堆
this.B = new PriorityQueue<>((x,y) -> (y-x));//修改比较器,成为大根堆
}
// 【维持堆数据平衡】,并保证小根堆A的最小值大于或等于大根堆B的最大值
// 即那边的数少,新元素加到那一边;
// 1、N=奇数,m>n, 即A的数多,应该放在B里;
// 放B中时,为了保证A的最小值>=B的最大值,先把数放在A,选出最小值的即A顶,再把A顶放到B
// 2、N=偶数,m=n, 即A和B的数一样多,默认放在A里;
// 放A中时,为了保证A的最小值>=B的最大值,先把数放在B,选出最大值的即B顶,再把B顶放到A
public void addNum(int num) {
if(A.size() != B.size()){//N=奇数,B少,放B
A.add(num);
B.add(A.poll());
}else {//N=偶数,一样多,放A
B.add(num);
A.add(B.poll());
}
}
public double findMedian() {//两个堆顶即是中位数所在的位置,即堆顶的数是整个范围的中间数
return A.size() == B.size() ? (A.peek()+B.peek())/2.0 : A.peek();
//注意:这里是2.0 不能是2,否则数会出错
}
}
图解:
class Solution {
public int maxSubArray(int[] nums) {
int res = nums[0];
for(int i=1;i<nums.length;i++){
//此时可把nums[i]看作是状态方程dp[],此题为了节省空间,直接在原数组修改
nums[i] += Math.max(nums[i-1],0);
//dp[i] = nums[i] + max(dp[i-1],0);dp[i-1]>0,则加上,否则+0,相当于不加
res = Math.max(res,nums[i]);
}
return res;
}
}
题目:
输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。
思路:递归
将一个数拆成不同的部分,然后递归再把每一部分拆成不同的部分…
函数f(n):表示1~n这n个整数中1出现的次数。
将n拆分为两部分,最高一位的数字high和其他位的数字last,分别判断情况后将结果相加。 其中最高位是1时比较特殊,需要考虑2部分:
1、最高位包含“1”的个数;
2、除去最高位,其他位含“1”的个数
举例:
1、最高位是1的情况:
class Solution {
public int countDigitOne(int n) {
return getnum(n);
}
public int getnum(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);//这里需要从double强转为int
int last = n - high*pow;
//其实就是最高位是1时需要考虑2部分:1、最高位包含“1”的个数;2、除去最高位,其他位含“1”的个数
if(high == 1){//最高位是1的情况
return getnum(pow-1) + last + 1 + getnum(last);// [1~999] + 【1000~1234】 + [234]
}else{// 最高位不是1的情况
return pow + high*getnum(pow-1) + getnum(last);
// [1~999] + 【1000~1999】 + [2000~2999] + [3000~3567] + [567]
}
}
}
如图,按照不同的位数可以分为不同的区间
第一步:求所在区间的起始数以及位数;
循环执行 nn 减去 一位数、两位数、… 的数位数量 count ,直至 n≤count 时跳出。由于 n 已经减去了一位数、两位数、…、(digit-1) 位数的 数位数量 count,因而此时的 n 是从起始数字 start 开始计数的。
第二步:确定所求数位所在的数字
所求数位 在从数字 start 开始的第 [(n - 1) / digit] 个 数字 中( start 为第 0 个数字)
第三步:确定所求数位在 num的哪一数位
所求数位为数字 num 的第 (n - 1) % digit 位( 数字的首个数位为第 0 位)
代码:
class Solution {
public int findNthDigit(int n) {
int digit = 1;//当前区间的位数,1、2、3、4......
long start = 1;//当前区间的起始数,0、10、100、1000......
long count = 9;//当前区间的数共占的位数,9、180、2700、36000......count=9*digit*start
//第一步:求所在区间的起始数以及位数
while(n>count){//当n<=count 跳出循环
n -= count;
digit += 1;
start *= 10;
count = 9*digit*start;
}
//第二步:找到区间上的那个数
long num = start + (n-1)/digit;
//第三步:找到这个数的第几位
int i = (n-1)%digit;
return Long.toString(num).charAt(i)-'0';
}
}
参考LeetCode179:最大数
class Solution {
public String largestNumber(int[] nums) {
int n =nums.length;
String[] arr = new String[n];
for(int i=0;i<n;i++){
arr[i] = String.valueOf(nums[i]);
}
//对数组arr按照拼接后的大小进行降序排列
//通过比较(a+b)和(b+a)的大小,就可以判断出a,b两个字符串谁应该在前面
//eg,[3,30,34]排序后变为[34,3,30];[233,23333]排序后变为[23333,233]
Arrays.sort(arr,(a,b)->{
return (b+a).compareTo(a+b);
});
//如果排序后的第一个元素是0,那后面的元素肯定小于或等于0,则可直接返回0
//但要注意equals和==的区别,前者判断值是否相等,后者判断引用地址是否相等
if(arr[0].equals("0")) return "0";
StringBuilder res = new StringBuilder();
for(int i=0;i<n;i++){
res.append(arr[i]);
}
return res.toString();
}
}
类似LeetCodeT91:解码方法
class Solution {
public int translateNum(int num) {
String s = String.valueOf(num);
int[] dp = new int[s.length()+1];//dp[]∈[0,num.length],多了一个dp[0]
dp[0] = 1;
dp[1] = 1;
for(int i=2;i<dp.length;i++){//i从第2个开始
String tmp = s.substring(i-2,i);//[i-2,i)
if(tmp.compareTo("10") >= 0 && tmp.compareTo("25")<=0){
dp[i] = dp[i-1] + dp[i-2];
}else{
dp[i] = dp[i-1];
}
}
return dp[s.length()];//返回最后一个数
}
}
class Solution {
public int translateNum(int num) {
String s = String.valueOf(num);
int a = 1, b = 1;
for(int i = 2; i <= s.length(); i++) {
String tmp = s.substring(i - 2, i);
int c = tmp.compareTo("10") >= 0 && tmp.compareTo("25") <= 0 ? a + b : a;
b = a;
a = c;
}
return a;
}
}
上题中是0-25代表字母,本题是1~26代表字母,且两位数不能以0开头,说明本题中就不能包含“0”,不管是1位数还是2位数;故每次需要判断是否包含0;上题用的if else ;本题是两个if ,因此在dp[i-1]的表示上略有差别
题目:
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
思路:动态规划
class Solution {
public int maxValue(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(i==0 && j==0) continue;
if(i == 0){//在第一行的情况
grid[i][j] += grid[i][j-1];
}else if(j == 0){//在第一列的情况
grid[i][j] += grid[i-1][j];
}else{//不在第一行也不在第一列
grid[i][j] += Math.max(grid[i][j-1],grid[i-1][j]);
}
}
}
return grid[m-1][n-1];
}
}
public int maxValue(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
for(int i = 1;i<m;i++){ grid[i][0] += grid[i-1][0];}
for(int j = 1;j<n;j++){ grid[0][j] += grid[0][j-1];}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
grid[i][j] += Math.max(grid[i-1][j],grid[i][j-1]);
}
}
return grid[m-1][n-1];
}
同LeetCodeT3
class Solution {
public int lengthOfLongestSubstring(String s) {
HashMap<Character,Integer> map = new HashMap<>();
int res = 0;
int tmp = 0;
int n = s.length();
for(int i = 0; i<n;i++){
//查看在当前字符之前与当前字符重复的字符所在的索引
int index = map.getOrDefault(s.charAt(i),-1);//获取指定key对应的value,没有的话默认为-1
map.put(s.charAt(i),i);
//map每次会更新,即使相同的key,这次的value会覆盖上次的value(index),即实现最近的index
tmp = i-index > tmp ? tmp+1 : i-index; // dp[i] = i-index > dp[i-1] ? dp[i-1]+1 : i-index
//d = i-index:当前字符和其最近重复字符的距离
//如果第i个字符之前没有出现过,index=-1,i-index>tmp,直接在上次的子串加入当前字符,即长度是dp[i] = dp[i-1]+1
//2.1、如果 d>dp[i-1],说明该重复字符位于上次出现在dp[i-1]对应的最长字符串之前,
// 因此不影响本次,直接在上次的子串加入当前字符,即长度是dp[i-1]+1
//2.2、如果 d<=dp[i-1],说明该重复字符位于上次出现在dp[i-1]对应的最长字符串中,
// 所以本次只能从该重复字符位置开始计算到当前字符,即长度是i-index
res = Math.max(res,tmp);
}
return res;
}
}
同LeetCodeT264
class Solution {
public char firstUniqChar(String s) {
HashMap<Character,Integer> map = new HashMap<>();
for(int i=0;i<s.length();i++){
map.put(s.charAt(i),map.getOrDefault(s.charAt(i),0)+1);//map的键存字符,值存这个字符出现的次数
}
for(int j =0;j<s.length();j++){
if(map.get(s.charAt(j)) == 1) return s.charAt(j);
//字符串从前往后遍历,当在map中该字符的次数是1是,说明只出现1此,同时也是第一个出现的
}
return ' ';
}
}
class Solution {
int count;
public int reversePairs(int[] nums) {
this.count = 0;
merge(nums,0,nums.length-1);
return count;
}
public void merge(int[] nums,int left, int right){
int mid = left + ((right-left)>>2);
if(left>=right) return ;
else{
merge(nums,left,mid);
merge(nums,mid+1,right);
mergeSort(nums,left,mid,right);
}
}
public int mergeSort(int[] nums, int left, int mid, int right){
int[] tmp = new int[right-left+1];
int index =0;
int i = left;
int j = mid+1;
while(i<=mid && j<=right){
if(nums[i] <= nums[j]){
tmp[index++] = nums[i++];
}else{//只有左边大于右边的才是逆序对,才统计个数
count += (mid-i+1);//加括号
tmp[index++] = nums[j++];
}
}
while(i<=mid){//当右边数组已经遍历完,把左边剩余的数移入数组
tmp[index++] = nums[i++];
}
while(j<=right){//当左边数组已经遍历完,把右边剩余的数移入数组
tmp[index++] = nums[j++];
}
//把新数组的数覆盖nums数组,
//其实就是把当前的nums进行排序,供下一次和其他的数组归并和计数count
for(int k =0;k<tmp.length;k++){
nums[k+left] = tmp[k];
}
return count;
}
}
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode node1 = headA;
ListNode node2 = headB;
while(node1 != node2){
node1 = node1!= null ? node1.next : headB;//这里判断node1!= null 而不是 node1.next != null
node2 = node2!= null ? node2.next : headA;
//假如两个链表没有公共节点,就把NULL当作它们的公共节点,所以两个判断是X!=NULL而不是X.next!=NULL
}
return node1;
}
}
class Solution {
public int search(int[] nums, int target) {
return helper(nums,target) - helper(nums,target-1);
}
public int helper(int[] nums, int target){
int i =0 ; int j = nums.length-1;
while(i<=j){//i,j是定位右边界的区间,即在[i,j]内找右边界,当i>j时,找到右边界了
int mid = (i+j)/2;
if(nums[mid] <= target){
//这里是<=,包含不存在target的情况;当nums[mid] <= target,说明右边界还在nums[mid]的右边
i = mid+1;
}else j = mid-1;//当nums[mid] > target,说明右边界在nums[mid]的左边
//就是不存在target,最后返回target-1后面的元素,其实就是target-1的右边界,两者做差=0
}
return i;//右边界找到了,i
}
}
1、问题简化:整型数组 nums 里除【 一个】 数字之外,其他数字都出现了两次
2、本题:nums 里除【 两个】 数字之外,其他数字都出现了两次
class Solution {
public int[] singleNumbers(int[] nums) {
int m =1;//不相等的二进制位,从右边第一位开始,即0001
int x =0;//x 存在于nums1 子数组
int y= 0;//y 存在于nums2 子数组
int n=0;//记录整个数组一起异或后的结果,即x⊕y的结果
//1、遍历异或得到n=x⊕y
for(int num : nums){
n ^= num;//^:java中的异或运算符,依次⊕数组的每一个数,最终得到x⊕y
}
//2、循环左移,计算m
while((n & m) == 0 ){
m<<=1;//m循环左移,即0001、0010、0100、1000.。。最终找到x和y第一个(从要往左)不相等二进制位
}
//3、分组,每组分别异或
for(int num:nums){
if((num & m)== 0) x^=num;//把数组中每个数中的m二进制位是0的分为一组nums1;然后对nums1异或得到x
//这里在for循环遍历时执行异或:x^=num,相当于对nums1的每个数轮流异或,下面的nums2同理
else y^=num;//把数组中每个数中的m二进制位是1的分为一组nums2;然后对nums2异或得到y
}
return new int[]{x,y};
}
}
eg:count= 10100011 –>res=00000001->00000011->00000110->00001100->00011000->00110001->01100010-> 11000101
class Solution {
public int singleNumber(int[] nums) {
int[] count = new int[32];//初始化count数组,存每个数的32个二进制位上的1的个数
//1、计算数组中所有数在每一个二进制位上1的计数
for(int num : nums){
for(int i =0;i<count.length;i++){
count[i] += (num & 1);
//count先对第一个数num的每一位进行 与“1”,然后对第二个数num每一位进行 与“1”,
//并和上一次的count结果相加,最终实现每一位的1的统计
num >>>= 1;//num 右移,结合上述num & 1操作,实现num的每一位与1与操作;
//num从右边第一位开始右移,直到左边第一位到达最右边,count[0]~[32]依次记录num从右往左的二进制位
}
}
//2、每个二进制位对m求余,m为题中每个数重复的次数
int res=0; int m =3;
for(int j=0;j<count.length;j++){
res<<=1;
res |= count[31-j]%m;
//|:或运算;eg count=10100011-->res=00000001->00000011->00000110->00001100->00011000->00110001->01100010->11000101
//每个二进制位对m求余,count[0]~[32]记录num从右往左的二进制位,需要倒回来
}
return res;
}
}
class Solution {
public int[][] findContinuousSequence(int target) {
int i =1;//初始化左边界的数是1
int j =2;//初始化右边界的数是2
int s = 3;//初始化区间[i,j]的和是1+2=3
List<int[]> res = new ArrayList<>();
while(i<j){//当i=j时退出循环;∵连续区间和=target,∴区间元素最多到target/2,即j<=target/2,i和j一直往右走,一直在增大,直到i=j=target/2时,就退出
if(s == target){
int[] ans = new int[j-i+1];//若区间数和=target,则返回这个区间的数组
for(int k = i;k<=j;k++){//k∈[i,j]
ans[k-i] = k;//∵k从i开始,∴k-i表示当前第几个的索引;
//∵是正整数序列,且i从1开始,∴当前索引位置的数 == 索引值
}
res.add(ans);
}
if(s<target){//如果
j++;//先扩大
s +=j;//再调整区间和
}else{//res>=target;
//>时,说明区间和大了,需要减小,即从左边开始去掉一位小的数;
//=时,添加当前区间之后,需要左边界右移一位,即遍历后面的情况; 故>和=都需要左边界右移
s -=i;//先去掉左边的数
i++;//指针右移
}
}
return res.toArray(new int[0][]);//list转为array
}
}
class Solution {
public String reverseLeftWords(String s, int n) {
if(s == null) return null;
int len = s.length();
if(n%len == 0) return s;
char[] words = s.toCharArray();
reverse(words,0,len-1);//先全反转
reverse(words,0,len-1-n);// 前边反转
reverse(words,len-n,len-1);//后边反转
return new String(words);
}
public void reverse(char[] arr ,int start, int end){
while(start<end){
char temp = arr[end];
arr[end--] = arr[start];
arr[start++] = temp;
}
}
}
重点步骤1:每轮窗口滑动移除了元素 nums[i - 1]时,需将 deque内的对应元素一起删除。
重点步骤2:每轮窗口滑动添加了元素 nums[j + 1]时,需将 deque内所有 < nums[j + 1] 的元素删除。
i∈[1−k,n−k] ,j∈[0,n−1]
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
int[] res = new int[n-k+1];//依次存储每个窗口的最大值
Deque<Integer> deque = new LinkedList<>();//头部存储当前窗口最大值
if(n == 0 || k==0){
return new int[0];
}
for(int j =0,i=1-k;j<n;i++,j++){//i∈[1−k,n−k] ,j∈[0,n−1]
if(i>0 && deque.peekFirst() == nums[i-1]) deque.removeFirst();
//形成窗口后,当窗口左边移除的数=deque头部
while(!deque.isEmpty() && deque.peekLast()<nums[j]) deque.removeLast();
//删除queue中<窗口右边添加的数
deque.addLast(nums[j]); //此时的j是移动后的,即j+1
if(i>=0) res[i] = deque.peekFirst();
//记录窗口最大值,当形成窗口时,j=0时直接执行这一步,即res[0]作为初始化的deque头部
}
return res;
}
}
class MaxQueue {
Queue<Integer> queue;//单队列,模拟给定的队列
Deque<Integer> deque;//双队列,头部存储当前队列最大值
public MaxQueue() {
queue = new LinkedList<>();
deque = new LinkedList<>();
}
public int max_value() {
return deque.isEmpty()? -1 : deque.peekFirst();
}
public void push_back(int value) {
queue.offer(value);//入队queue
while(!deque.isEmpty() && deque.peekLast()<value) deque.pollLast();//deque朝【右】弹出
deque.offerLast(value);//deque 入队value
}
public int pop_front() {
if(queue.isEmpty()) return -1;
if(queue.peek().equals(deque.peekFirst())) deque.pollFirst();//这里用equals(),不能用 ==
//若queue弹出的数和deque首部数相同,则朝【左】弹出deque首部数
return queue.poll();
}
}
/**
* Your MaxQueue object will be instantiated and called as such:
* MaxQueue obj = new MaxQueue();
* int param_1 = obj.max_value();
* obj.push_back(value);
* int param_3 = obj.pop_front();
*/
思路:动态规划
1、设输入 n个骰子的解(即概率列表)为 f(n) ,其中「点数和」 x 的概率为 f(n,x) 。
2、由于新增骰子的点数只可能为 1 至 6 ,因此概率f(n−1,x) 仅与 f(n,x+1) , f(n, x + 2), … , f(n,x+6) 相关。因而,遍历 f(n−1) 中各点数和的概率,并将其相加至f(n) 中所有相关项,即可完成f(n−1) 至 f(n) 的递推。
3、将 f(i) 记为动态规划列表形式 dp[i];
代码:
class Solution {
public double[] dicesProbability(int n) {
double[] dp = new double[6];//初始化dp
Arrays.fill(dp,1.0/6.0);//dp=[1/6,1/6,1/6,1/6,1/6,1/6],即只有一个骰子时的各点数和概率
for(int i=2;i<=n;i++){//假设骰子总数是i时
double[] tmp = new double[5*i+1];
//当前所有点数和对应的概率;i个骰子的点数和区间[i*1,i*6],共6i-i+1=5i+1个
for(int j=0;j<dp.length;j++){//当前i个骰子时,dp.length为上一次(i-1)的dp[]长度
//拿i-1个骰子的点数之和数组的第j个值,它所影响的是i个骰子时的temp[j+k]的值
for(int k=0;k<6;k++){
tmp[j+k] += dp[j] *1.0/6.0;
//这里记得是加上dp数组值与1/6的乘积,1/6是第i个骰子投出某个值的概率
}
}
dp = tmp;//i个骰子的点数之和全都算出来后,要将tmp数组移交给dp数组,
//dp数组代表i个骰子时的可能出现的点数之和的概率;用于计算i+1个骰子时的点数之和的概率
}
return dp;
}
}
class Solution {
public boolean isStraight(int[] nums) {
Set<Integer> repeat = new HashSet<>();
int max = 0, min = 14;
for(int num : nums) {
if(num == 0) continue; // 跳过大小王
max = Math.max(max, num); // 最大牌
min = Math.min(min, num); // 最小牌
if(repeat.contains(num)) return false; // 若有重复,提前返回 false
repeat.add(num); // 添加此牌至 Set
}
return max - min < 5; // 最大牌 - 最小牌 < 5 则可构成顺子
}
}
class Solution {
public boolean isStraight(int[] nums) {
int joker = 0;
Arrays.sort(nums); // 数组排序
for(int i = 0; i < 4; i++) {
if(nums[i] == 0) joker++; // 统计大小王数量
else if(nums[i] == nums[i + 1]) return false; // 若有重复,提前返回 false
}
return nums[4] - nums[joker] < 5; // 最大牌 - 最小牌 < 5 则可构成顺子
}
}
总结一下推导公式:(此轮中num下标 + m) % 上轮元素个数 = 上轮num的下标
class Solution {
public int lastRemaining(int n, int m) {
int ans = 0;//最后一轮中只剩一个数,也就是只有一个索引,即0
for(int i = 2;i<=n;i++){
//倒数第二轮中有2个数,因此i从2开始,直到i=n,回到原始数组,即还没开始删除
ans = (ans + m) % i;//用这一轮的索引反推上一轮的索引
}
return ans;
}
}
class Solution {
public int maxProfit(int[] prices) {
int cost = Integer.MAX_VALUE;//记录有史以来最低的价格
int profit = 0;//初始化利润为0
for(int price : prices){
cost = Math.min(price,cost);//比较当前价格和历史的最低价格
profit = Math.max(profit,price-cost);//比较前一日的最大利润,和当日可获得的最大利润
}
return profit;
}
}
class Solution {
public int sumNums(int n) {
boolean flag = n>1 && (n += sumNums(n-1))>0;
//只有n>1,才会执行后面的递归;否则flag=false,return n;
return n;
}
}
题目:
写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。
循环终止条件:进位c=0;
本题原理是利用位运算计算两个数,如求5+7,先通过位运算转换为10+2(进位+无进位和),再转换为4+8(进位+无进位和),再转换为0+12(进位+无进位和),此时仅为c=0,直接输出无进位和,即12;
class Solution {
public int add(int a, int b) {
//存储进位
while(b != 0){// 当进位为 0 时跳出
int c = (a&b)<<1;//存储进位,这里记得加括号,不然先执行左移
a = a^b;//无进位和 赋给a
b = c;
}
return a;
}
}
class Solution {
public int[] constructArr(int[] a) {
int n = a.length;
if(n==0) return new int[0];
int[] b = new int[n];
b[0] = 1;
int tmp =1;
for(int i=1;i<b.length;i++){
b[i] = b[i-1]*a[i-1];//左边的乘积和,即a[0]*a[1]...*a[i-1]
}
for(int i = b.length-2;i>=0;i--){
//从倒数第2个开始,
//即先算a[n-2]*a[n-1],再算a[n-3]*a[n-2]*a[n-1],再a[n-4]*a[n-3]*a[n-2]*a[n-1]
tmp *= a[i+1];//右边的乘积和,即a[i+1]*a[i+2]...*a[n-1]
b[i] *= tmp;//让左左边 * 右边,即a[0]*...*a[i-1] * a[i+1]*...*a[n-1]
}
return b;
}
}
类似的溢出问题:LeetCodeT7
可解释溢出位 >7 ?
因为本题要求>MAX_VALUE,输出MAX_VALUE;
如果这一轮的结果res溢出了,res会变为其他的小于MAX_VALUE的数,不会报错但是结果发生紊乱,使得在后面的循环中根据这个错误的res一错再错;因此为了避免本轮结果溢出,用上一轮的拼接结果res和这一轮的数字联合比较本轮拼接是否溢出
代码:
class Solution {
public int strToInt(String str) {
char[] num = str.trim().toCharArray(); //trim()函数移除字符串两侧的空白字符或其他预定义字符
int n = num.length;
if(n == 0) return 0;
int res = 0;//记录当前转换后的数字
int sign = 1;//记录第一位的符号:-:-1;+:+1
int binary = Integer.MAX_VALUE/10;
//如果当前数已经超过MAX_VALUE,那么他就会溢出报错,而我们是要判断当期数是否溢出,所以将当前数拆为倒数一位和其余各位
int i=1;
if(num[0]=='-') sign = -1;//sign=-1时,第一位是负号,第二位才是数字,所以从i=1开始
else if(num[0] != '+') i=0;//若是+,不用改变sign和i。因为sign和i初始化为1,
//但是是其他字符时需要i=0,即从第一个开始判断
for(int j=i;j<num.length;j++){
if(num[j]>'9' || num[j]<'0') break;
if(res>binary || res==binary && num[j]>'7') return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
res = 10*res + (num[j]-'0');
}
return sign*res;//sign=1返回res,sign=-1,返回-res
}
}
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
while(root != null){
if(root.val>p.val && root.val>q.val){//p,q都在root的左子树
root = root.left;
}else if(root.val<p.val && root.val<q.val){//p,q都在root的右子树
root = root.right;
}else break;//p,q分别在root的作用两侧,直接跳出
}
return root;
}
}
思路:先序遍历
本题和上题的区别:本题是二叉树不是二叉搜索树,无法根据节点值的大小判断在左\右子树,因此,考虑通过递归对二叉树进行先序遍历,当遇到节点 p 或 q 时返回。从底至顶回溯,当节点 p, q在节点 root的异侧时,节点 root 即为最近公共祖先,则向上返回 root 。
代码:
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if( root ==null || root == p || root == q) return root;// 如果树为空,直接返回null
//如果p和q中有等于当前root的(先序),那么它们的最近公共祖先即为root(一个节点也可以是它自己的祖先)
TreeNode left = lowestCommonAncestor(root.left,p,q);
// 递归遍历左子树,只要在左子树中找到了p或q,则先找到谁就返回谁
TreeNode right = lowestCommonAncestor(root.right,p,q);
// 递归遍历右子树,只要在右子树中找到了p或q,则先找到谁就返回谁
if(left == null && right == null) return null;//1、左右子树都为空,说明不存在p、q
if(left == null) return right;//3、如果在左子树中p和q都找不到,则 p和 q一定都在右子树中
if(right == null) return left;//4、如果在右子树中p和q都找不到,则 p和 q一定都在左子树中
return root;//2、当left和right均不为空,说明 p、q节点分别在root异侧, root是最近公共祖先
}
}