赞
踩
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
思路
使用hashmap,通过使用哈希的特性来完成
代码
class Solution {
public int[] twoSum(int[] nums, int target) {
HashMap<Integer,Integer> hasht = new HashMap<Integer,Integer>();
for(int i=0;i<nums.length;i++){
int x = target - nums[i];
if(hasht.get(x) != null){
int[] res = {hasht.get(x),i};
return res;
}
hasht.put(nums[i],i);
}
return new int[0];
}
}
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 “”。
思路
先扫描一遍找到最小长度的字符串,用此字符串做板子进行扫描。其时间复杂度为 min(字符串长度)* n
代码
class Solution { public String longestCommonPrefix(String[] strs) { int p = -1; int minp = 201; for(int i = 0;i < strs.length;i++){ if(strs[i].length() < minp){ p = i; minp = strs[i].length(); } } int res = 0; for(int i = 0;i <strs[p].length();i++){ for(int j = 0; j<strs.length; j++) if(strs[p].charAt(i) != strs[j].charAt(i)){ return strs[p].substring(0, res); } res++; } return strs[p].substring(0, res); } }
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
思路
跟两数之和一样,这里我们先进行排序,然后遍历数组 设置left = i+1,right为长度-1。得出所求。
代码
class Solution { public List<List<Integer>> threeSum(int[] nums) { List<List<Integer>> res = new ArrayList<>(); Arrays.sort(nums); for(int i = 0;i < nums.length;i++){ if(nums[i] > 0) return res; if(i > 0 && nums[i] == nums[i-1]) continue; int l = i + 1; int r = nums.length - 1; while(l<r){ if(nums[l]+nums[r]+nums[i]>0){ r--; }else if(nums[l]+nums[r]+nums[i]<0){ l++; }else{ List<Integer> list = new ArrayList<>(); list.add(nums[i]); list.add(nums[l]); list.add(nums[r]); res.add(list); while(l < r && nums[l+1] == nums[l]) ++l; while (l < r && nums[r-1] == nums[r]) --r; ++l; --r; } } } return res; } }
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
思路
同三数之和,这里不过是多加了一层循环!注意要使用一些剪枝策略来避免无效计算。
代码
class Solution { public List<List<Integer>> fourSum(int[] nums, int target) { List<List<Integer>> quadruplets = new ArrayList<List<Integer>>(); if (nums == null || nums.length < 4) { return quadruplets; } Arrays.sort(nums); int length = nums.length; for (int i = 0; i < length - 3; i++) { if (i > 0 && nums[i] == nums[i - 1]) { continue; } if ((long) nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) { break; } if ((long) nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] < target) { continue; } for (int j = i + 1; j < length - 2; j++) { if (j > i + 1 && nums[j] == nums[j - 1]) { continue; } if ((long) nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) { break; } if ((long) nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target) { continue; } int left = j + 1, right = length - 1; while (left < right) { long sum = (long) nums[i] + nums[j] + nums[left] + nums[right]; if (sum == target) { quadruplets.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right])); while (left < right && nums[left] == nums[left + 1]) { left++; } left++; while (left < right && nums[right] == nums[right - 1]) { right--; } right--; } else if (sum < target) { left++; } else { right--; } } } } return quadruplets; } }
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
思路
递归方法,我们递归遍历整个链表,并定义一个int类型的数用来判断删除哪一个结点,每次return时,该数++,当与给定的值相等时,则返回next结点。
代码
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { int cur = 0; public ListNode removeNthFromEnd(ListNode head, int n) { if(head==null) return null; head.next = removeNthFromEnd(head.next,n); cur++; if(n == cur) return head.next; return head; } }
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
思路
看到题目以后立马想到递归的方法,对于该题我们递归最底层为判断list1或者list2是否为空,返回剩下的list1或者list2。通过对比当前list1和list2的值来判断指针是否要前进一步,即.next。
代码
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode mergeTwoLists(ListNode list1, ListNode list2) { if(list1 == null){ return list2; } if(list2 == null){ return list1; } if(list1.val < list2.val){ list1.next = mergeTwoLists(list1.next,list2); return list1; }else{ list2.next = mergeTwoLists(list1,list2.next); return list2; } } }
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。
思路
一个个遍历
代码
class Solution { public int strStr(String ss, String pp) { int n = ss.length(), m = pp.length(); char[] s = ss.toCharArray(), p = pp.toCharArray(); for (int i = 0; i <= n - m; i++) { int a = i, b = 0; while (b < m && s[a] == p[b]) { a++; b++; } if (b == m) return i; } return -1; } }
请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
注意:
一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
空白格用 ‘.’ 表示。
思路
设置三个布尔数组分别记录某行/某列/3*3宫格,某位数字是否已经被摆放记录某列。然后分别进行判断。
代码
class Solution { public boolean isValidSudoku(char[][] board) { boolean[][] row = new boolean[9][9]; boolean[][] col = new boolean[9][9]; boolean[][] block = new boolean[9][9]; for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { if (board[i][j] != '.') { int num = board[i][j] - '1'; int blockIndex = i / 3 * 3 + j / 3; if (row[i][num] || col[j][num] || block[blockIndex][num]) { return false; } else { row[i][num] = true; col[j][num] = true; block[blockIndex][num] = true; } } } } return true; } }
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
思路
动态规划题,感觉不像是一个困难题,我们对列进行操作,我们只需要记住每一列左边的最大值和右边的最大值就好了,其水为他们两者之间最小值减去本身带的值。
代码
class Solution { public int trap(int[] height) { if(height.length == 1){ return 0; } int sum = 0; int[] left = new int[height.length]; int[] right = new int[height.length]; for(int i = 1; i < height.length -1;i++){ left[i] = Math.max(left[i-1], height[i-1]); } for (int i = height.length - 2; i >= 0; i--) { right[i] = Math.max(right[i + 1], height[i + 1]); } for (int i = 1; i < height.length - 1; i++) { int min = Math.min(left[i], right[i]); if (min > height[i]) { sum = sum + (min - height[i]); } } return sum; } }
给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。
注意:不能使用任何内置的 BigInteger 库或直接将输入转换为整数。
思路
注意本题只能用数组存储,因为其他整数类型存不下。所以我们得考虑进位问题,按照之前学的竖式乘法。
代码
class Solution { public String multiply(String num1, String num2) { char[] arr1 = num1.toCharArray(); char[] arr2 = num2.toCharArray(); int n = arr1.length, m = arr2.length; int[] res = new int[m + n]; // 每次相乘得到的结果,直接添加到最后的结果数组中,需要注意进位和结果数组下标 for(int i = m - 1; i >= 0; i --){ int index = res.length - 1 - (m - 1 - i); int carry = 0; // carry存储进位 for(int j = n - 1; j >= 0; j --){ int temp = toNum(arr2[i]) * toNum(arr1[j]) + carry + res[index]; res[index --] = temp % 10; carry = temp / 10; } while(index >= 0 && carry != 0){ int temp = res[index] + carry; res[index --] = temp % 10; carry = temp / 10; } } int index = 0; // 去除前导0 while(index < res.length && res[index] == 0){ index ++; } StringBuffer buffer = new StringBuffer(); for( ; index < res.length; index ++){ buffer.append(res[index]); } return buffer.length() == 0 ? "0" : buffer.toString(); } private int toNum(char c){ return c - '0'; } }
给你一个非负整数数组 nums ,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
假设你总是可以到达数组的最后一个位置。
思路
注意到走到最后一个位置,所以最后一个位置的值我们不用管。我们可以贪心,选取在步数内可以走的最远的位置 即maxpos = Math.max(maxpos, i+nums[i]);然后我们选取这个值,接着走。以此取到最小的step值
代码
class Solution {
public int jump(int[] nums) {
int end = 0;
int step = 0;
int maxpos = 0;
for(int i = 0;i<nums.length-1;i++){
maxpos = Math.max(maxpos, i+nums[i]);
if(i == end){
end = maxpos;
step++;
}
}
return step;
}
}
给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
思路
我们可以考虑先转置以后进行观察,发现我们只需要对每行进行调转就可以了
代码
class Solution { public void rotate(int[][] matrix) { for(int i = 0; i < matrix.length; i++){ for(int j = 0; j < i; j++){ int temp = matrix[i][j]; matrix[i][j] = matrix[j][i]; matrix[j][i] = temp; } } int m = matrix[0].length; for(int i = 0; i < matrix.length; i++){ for(int j = 0; j < m/2; j++){ int temp = matrix[i][j]; matrix[i][j] = matrix[i][m-j-1]; matrix[i][m-j-1] = temp; } } } }
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
思路
使用动态规划。目标是找到动态规划表达式,我们考虑到如果都是正整数的话,那我们为fn = fn + nums[i],但不是这样的。但是存在负数 又考虑到是连续的,则表达式改为max(fn + nums[i],nums[i])如果nums[i]大于fn + nums[i] 即fn为负值我们可以完全舍弃,另考虑到我需要有个变量 存储最大的值。则有res = max(fn,res)。
代码
class Solution {
public int maxSubArray(int[] nums) {
int fn = -1;
int res = Integer.MIN_VALUE;
for(int s:nums){
fn = Math.max(fn+s,s);
res = Math.max(fn,res);
}
return res;
}
}
给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
思路
我们可以计算旋转几次(即花几个圈),然后以此向左走向下走向右走向上走。这里要注意边界条件(巧用旋转几次)
代码
class Solution { public List<Integer> spiralOrder(int[][] matrix) { List<Integer> res = new ArrayList<>(); int m = matrix.length; int n = matrix[0].length; int mnlen = Math.min(m, n) % 2 + Math.min(m, n) / 2; for(int i = 0; i < mnlen; i++){ // 向左 for(int j = i;j < n-i;j++){ res.add(matrix[i][j]); } for(int j = i+1; j < m-i; j++){ res.add(matrix[j][n-i-1]); } for(int j = n-i-2;j >= i && (m-1-i != i); j--){ res.add(matrix[m-1-i][j]); } for(int j = m-i-2;j >= i+1 && (n-1-i) != i; j--){ res.add(matrix[j][i]); } } return res; } }
给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
思路
这题,我们可以进行遍历。假设有个机器人,我们先把机器人放在nums[0],其能量也为nums[0],然后机器人进行行走(这里使用i进行计数),每走一步能量减一,如果当前点的nums[i]的能量大于机器人的能量,我们则进行更换。然后继续走 如果走不到了 则返回false,不然返回true。
代码
class Solution { public boolean canJump(int[] nums) { int cur = nums[0]; int i = 1; for(i = 1;i<nums.length;i++){ if(cur!=0){ cur--; if(cur<nums[i]){ cur = nums[i]; } }else{ return false; } } return i==nums.length; } }
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
思路
使用动态规划,考虑到最后一个只能由上面 或者左边过来,则方法是这两个的和。即a[i][j] = a[i-1][j] + a[i][j-1];考虑边界条件,只要ij其中一个为0,则方法只有一种。
优化 其实可以只用m的空间 即可完成。
代码
class Solution { public int uniquePaths(int m, int n) { int[][] a = new int[m][n]; if(m==1||n==1){ return 1; } for(int i=0;i<m;i++){ a[i][0]=1; } for(int i=0;i<n;i++){ a[0][i]=1; } for(int i=1;i<m;i++){ for(int j=1;j<n;j++){ a[i][j] = a[i-1][j] + a[i][j-1]; } } return a[m-1][n-1]; } }
优化代码
class Solution { public int uniquePaths(int m, int n) { int[] a = new int[n]; if(m==1||n==1){ return 1; } a[0] = 1; for(int i=1;i<n;i++){ a[i]=1; } for(int i=1;i<m;i++){ a[0] = 1; for(int j=1;j<n;j++){ a[j] = a[j-1] + a[j]; } } return a[n-1]; } }
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
思路
动态规划题,想到当你走到最后一个楼梯时,要么是从n-1上来要么就是从n-2上来
则有表达式fn = fn-1 + fn-2,初值即为一层和二层楼梯次数。我们使用数组遍历(想一想其实只需要三个变量即可)
代码
class Solution {
public int climbStairs(int n) {
int[] a = new int[n];
if(n <= 2){
return n;
}
a[0] = 1;
a[1] = 2;
for(int i=2;i<n;i++){
a[i] = a[i-1]+a[i-2];
}
return a[n-1];
}
}
优化代码
class Solution { public int climbStairs(int n) { if(n <= 2){ return n; } int a = 1; int b = 2; int c = 0; for(int i=2;i<n;i++){ c = a + b; a = b; b = c; } return c; } }
给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。
思路
我们使用第一行和第一列来存储出现的0;另外我们设置两个变量用来存储判断第一行或者第一列中是否出现0。
代码
class Solution { public void setZeroes(int[][] matrix) { Boolean flag1 = false; Boolean flag2 = false; for(int i = 0;i<matrix.length;i++){ if(matrix[i][0] == 0){ flag1 = true; } } for(int i = 0;i<matrix[0].length;i++){ if(matrix[0][i] == 0){ flag2 = true; } } for(int i = 1; i < matrix.length; i++){ for(int j = 1; j < matrix[0].length; j++){ if(matrix[i][j] == 0){ matrix[i][0] = matrix[0][j] = 0; } } } for(int i = 1; i < matrix.length; i++){ for(int j = 1; j < matrix[0].length; j++){ if (matrix[i][0] == 0 || matrix[0][j] == 0) { matrix[i][j] = 0; } } } if(flag1){ for(int i = 0;i<matrix.length;i++){ matrix[i][0] = 0; } } if(flag2){ for(int i = 0;i<matrix[0].length;i++){ matrix[0][i] = 0; } } } }
给你一个整数数组 nums ,数组中的元素互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
思路
仔细看例子给出的输出,每新增一个数字,相当于前者的集合加上该个数字,比如一开始【】,数字1进来了 则有【1】,接着数字2进来,注意前面有{【】,【1】} 那则有新增的【2】,【1,2】以此类推。
代码
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
res.add(new ArrayL ist<>());
for(int i = 0; i<nums.length; i++){
int all = res.size();
for(int j = 0; j < all; j++){
List<Integer> temp = new ArrayList<>(res.get(j));
temp.add(nums[i]);
res.add(temp);
}
}
return res;
}
}
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
思路
从后排就好,每次把最大值放后面。我们设置n>0,这样的话num2排完了那我们就不用排了(因为num1的数组就存在当中)。
代码
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int last = m+n-1;
while(n > 0){
if(m==0||nums1[m-1]<=nums2[n-1]){
nums1[last--]=nums2[--n];
}else{
nums1[last--]=nums1[--m];
}
}
}
}
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
节点的左子树只包含 小于 当前节点的数。
节点的右子树只包含 大于 当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
思路
使用中序遍历,递归方法和栈方法都可以。
代码
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { double pre = -Double.MAX_VALUE; public boolean isValidBST(TreeNode root) { return inOrder(root); } public boolean inOrder(TreeNode root){ if(root == null) { return true; } boolean left = inOrder(root.left); if(root.val<= pre){ return false; } pre = root.val; boolean right = inOrder(root.right); return left && right; } }
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
思路
其实就是BFS,我们可以用队列实现,先进先出。
代码
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { public List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> res = new ArrayList<>(); Queue<TreeNode> queue = new ArrayDeque<>(); if(root == null){ return res; } queue.add(root); while(!queue.isEmpty()){ List<Integer> listRes = new ArrayList<>(); int n = queue.size(); for(int i = 0;i<n;i++){ TreeNode node = queue.poll(); listRes.add(node.val); if(node.left!=null){ queue.add(node.left); } if(node.right!=null){ queue.add(node.right); } } res.add(listRes); } return res; } }
给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
思路
利用其性质,每个数是它左上方和右上方的数的和
代码
class Solution { public List<List<Integer>> generate(int numRows) { List<List<Integer>> res = new ArrayList<>(); int[][] ar=new int[numRows][numRows]; for (int i = 0; i < numRows; i++) { ar[i][0]=1; } for(int i = 0; i<numRows;i++){ List<Integer> reslist = new ArrayList<>(); reslist.add(1); for(int j = 1; j <= i; j++){ ar[i][j] = ar[i-1][j] + ar[i-1][j-1]; reslist.add(ar[i][j]); } res.add(reslist); } return res; } }
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
思路
巧用最大最小值,最大值级利润,即0和当前值减去最小值,考虑到不能在买入前卖出股票。我们先求最大值,再求最小值。最小值即比较两两直接的最小值。
代码
class Solution {
public int maxProfit(int[] prices) {
if(prices.length <= 1){
return 0;
}
int maxl = 0;
int minl = prices[0];
for(int i=0; i<prices.length; i++){
maxl = Math.max(maxl,prices[i]-minl);
minl = Math.min(minl,prices[i]);
}
return maxl;
}
}
给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
思路
动态规划题,在这里设置两个状态,0是持有现金,1是持有股票。我们不断更新这两个状态得到最终解。
代码
public class Solution { public int maxProfit(int[] prices) { int len = prices.length; if (len < 2) { return 0; } // 0:持有现金 // 1:持有股票 int[][] dp = new int[len][2]; dp[0][0] = 0; dp[0][1] = -prices[0]; for (int i = 1; i < len; i++) { dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]); } return dp[len - 1][0]; } }
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
思路
双指针题,我们设置一个快指针fast 和一个慢指针slow,设定fast每次走两个,slow每次走一格
假设slow走x格以后他们相遇
此时fast走了2x格 设置 a为未进入循环的步长 b为循环步长 z为当前进入循环以后走的步长
有 x = a+z,2x = a+nb+z
n为循环的次数 我们暂时设置为1 则有
x = a + z ,2x = a+b+z
因为相遇 易知 x = b 又有 b = a+z
我们目前要求出a
我们利用头结点,同速度和slow跑易知a步以后他们会相遇 为什么?
因为 当走了a 步以后
此时 head a
slow a+a+z 即a + b
b为循环,则可求
此外对于 没有循环 我们只需要判断fast 和fast的next是否为空即可
代码
/** * Definition for singly-linked list. * class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public 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; } } while(head != slow){ head = head.next; slow = slow.next; } return head; } }
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
思路
直接用快速排序就好了 平均复杂度为Onlogn
代码
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode sortList(ListNode head) { return quickSort(head); } public ListNode quickSort(ListNode head) { if (head==null || head.next==null) { return head; } ListNode h1 = new ListNode(); ListNode h2 = new ListNode(); ListNode h3 = new ListNode(); ListNode t1 = h1; ListNode t2 = h2; ListNode t3 = h3; ListNode curr = head; int pivot = getMid(head).val; while (curr!=null) { ListNode next = curr.next; if (curr.val < pivot) { curr.next = null; t1.next = curr; t1 = t1.next; } else if (curr.val == pivot) { curr.next = null; t2.next = curr; t2 = t2.next; } else { curr.next = null; t3.next = curr; t3 = t3.next; } curr = next; } h1 = quickSort(h1.next); h3 = quickSort(h3.next); h2 = h2.next; t2.next = h3; if (h1==null) { return h2; } else { t1 = h1; while (t1.next!=null) { t1 = t1.next; } t1.next = h2; return h1; } } public ListNode getMid(ListNode head) { ListNode fast = head; ListNode slow = head; while (fast!=null && fast.next!=null) { fast = fast.next.next; slow = slow.next; } return slow; } }
给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
测试用例的答案是一个 32-位 整数。
子数组 是数组的连续子序列。
思路
这里相比于之前
fmax = max{fmaxai,ai}我们还需要考虑fmin的值,因为存在负数,那我们更改方程。fmax = max{fmaxai,fminai,ai}同时有fmin = min{fmaxai,fmin*ai,ai}每次存储fmin的值 (因为负数不一定是坏事)
代码
class Solution {
public int maxProduct(int[] nums) {
int fmin = nums[0];
int fmax = nums[0];
int res = fmax;
for(int i = 1; i < nums.length; ++i){
int mx = fmax, mn = fmin;
fmax = Math.max(Math.max(mx*nums[i],mn*nums[i]),nums[i]);
fmin = Math.min(Math.min(mx*nums[i],mn*nums[i]),nums[i]);
res = Math.max(fmax, res);
}
return res;
}
}
给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。
以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
思路
只使用常量级的额外空间。且排序好的,立马想到使用双指针left和right,分别只想0和length-1。如果大了,则r指针左移,小了则l指针右移。
代码
class Solution { public int[] twoSum(int[] numbers, int target) { int l = 0; int r = numbers.length - 1; while(l<r){ if(numbers[l]+numbers[r]>target){ r--; }else if(numbers[l]+numbers[r]<target){ l++; }else{ return new int[]{l+1,r+1}; } } return new int[]{l+1,r+1}; } }
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
思路
动态规划题,我们先找到表达式,很容易得出fn = fn-2 + fn-3 + cost[n]。我们分别求出其初值。最后!我们取最后两个数的最大值。这里是因为 考虑到 最后的解肯定是从倒数两个数来的(因为倒数第三个数还可以加上倒数第一个数)
代码
class Solution { public int rob(int[] nums) { int n = nums.length; if(n == 1){ return nums[0]; }else if(n == 2){ return Math.max(nums[0],nums[1]); }else if(n == 3){ return Math.max(nums[0]+nums[2],nums[1]); } int[] a = new int[n]; a[0] = nums[0]; a[1] = nums[1]; a[2] = nums[0]+nums[2]; for(int i=3;i<n;i++){ a[i] = Math.max(a[i-2],a[i-3])+nums[i]; } return Math.max(a[n-1],a[n-2]); } }
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
思路
基于dfs来做,巧用标记,标记好已经走过的。然后我们遍历整个数组,当岛没有访问过,即存在为1。
我们res++,然后进行dfs使得这块岛全部被标记。
代码
class Solution { public int numIslands(char[][] grid) { int res = 0; for(int i=0;i<grid.length;i++){ for(int j=0;j<grid[0].length;j++){ if(grid[i][j] == '1'){ dfs(grid,i,j); res++; } } } return res; } public void dfs(char[][] grid,int x,int y){ if(x < 0||y < 0||x>=grid.length||y>=grid[0].length) return ; if(grid[x][y] != '1') return; grid[x][y] = '2'; dfs(grid,x-1,y); dfs(grid,x+1,y); dfs(grid,x,y-1); dfs(grid,x,y+1); } }
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
思路
使用快慢指针,如果是循环的 那fast指针一定可以追上slow指针,跟之前检查环一样。然后我们判断是否为1.
代码
class Solution { public boolean isHappy(int n) { int fast = n; int slow = n; do{ slow = square(slow); fast = square(fast); fast = square(fast); }while(fast!=slow); if(fast == 1){ return true; }else{ return false; } } public int square(int fs){ int squaresum = 0; while(fs!=0){ squaresum += (fs%10)*(fs%10); fs /= 10; } return squaresum; } }
给定两个字符串 s 和 t ,判断它们是否是同构的。
如果 s 中的字符可以按某种映射关系替换得到 t ,那么这两个字符串是同构的。
每个出现的字符都应当映射到另一个字符,同时不改变字符的顺序。不同字符不能映射到同一个字符上,相同字符只能映射到同一个字符上,字符可以映射到自己本身。
思路
首先想到的就是用HashMap,因为用到唯一性,可以先判断元素是否存在 如果存在 判断映射是否和t中元素相等。考虑到s和t都是唯一性,我们建立两个hashmap
改进思路
可以类似用find函数(indexof),查找每个字符首次的出现的位置,如果相等则继续,不相等判断false。
代码
class Solution { public boolean isIsomorphic(String s, String t) { HashMap<Character, Character> sHash = new HashMap<Character, Character >(); HashMap<Character, Character> tHash = new HashMap<Character, Character >(); for(int i=0;i < s.length();i++){ if(sHash.get(s.charAt(i)) == null){ sHash.put(s.charAt(i), t.charAt(i)); }else{ if(sHash.get(s.charAt(i))!=t.charAt(i)){ return false; } } } for(int i=0;i < s.length();i++){ if(tHash.get(t.charAt(i)) == null){ tHash.put(t.charAt(i), s.charAt(i)); }else{ if(tHash.get(t.charAt(i))!=s.charAt(i)){ return false; } } } return true; } }
class Solution {
public boolean isIsomorphic(String s, String t) {
for(int i = 0;i<s.length();i++){
if(s.indexOf(s.substring(i,i+1))!=t.indexOf(t.substring(i,i+1))){
return false;
}
}
return true;
}
}
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
思路
考虑到利用两个指针一个pre,next。分别用于存储之前的值和后一个值,我们通过不断移动 得到想要的结果。
代码
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode reverseList(ListNode head) { ListNode prev = null; while(head != null){ ListNode next = head.next; head.next = prev; prev = head; head = next; } return prev; } }
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额
思路
依旧是fn = max(fn-1,fn-2+cost【n】),这里我们要做两次 一次从2开始 一次走到n-1结束 取最大值
代码
class Solution { public int rob(int[] nums) { int n = nums.length; if(n <= 1){ return nums[0]; } int[] dp1 = new int[n]; dp1[0] = 0; dp1[1] = nums[1]; for(int i = 2 ;i < n;i++){ dp1[i] = Math.max(dp1[i-1],(dp1[i-2]+nums[i])); } int[] dp2 = new int[n]; dp2[0] = nums[0]; dp2[1] = Math.max(nums[0],nums[1]); for(int i = 2 ;i < n-1;i++){ dp2[i] = Math.max(dp2[i-1],(dp2[i-2]+nums[i])); } return Math.max(dp2[n-2],dp1[n-1]); } }
给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false 。
思路
使用哈希表就可以
class Solution {
public boolean containsDuplicate(int[] nums) {
HashMap<Integer, Integer> res = new HashMap<Integer, Integer>();
for(int i = 0;i<nums.length;i++){
if(res.get(nums[i]) == null){
res.put(nums[i],i);
}else{
return true;
}
}
return false;
}
}
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
思路
使用快慢指针(双指针)先使用快慢指针找到中间的位置,对中间后面的进行倒转。然后方便进行比较。
代码
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public boolean isPalindrome(ListNode head) { ListNode slow = head,fast = head,prev = null; while(fast != null && fast.next != null){ slow = slow.next; fast = fast.next.next; } while(slow != null){ ListNode temp = slow.next; slow.next = prev; prev = slow; slow = temp; } while(head!=null&&prev!=null){ if(head.val != prev.val){ return false; } head = head.next; prev = prev.next; } return true; } }
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
思路
因为是二叉搜索树,从root开始进行判断就好了。
代码
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { while(true){ if(root.val>p.val && root.val>q.val){ root = root.left; }else if(root.val<p.val && root.val<q.val){ root = root.right; }else{ break; } } return root; } }
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数
思路
二分查找
代码
/* The isBadVersion API is defined in the parent class VersionControl. boolean isBadVersion(int version); */ public class Solution extends VersionControl { public int firstBadVersion(int n) { int left = 0; int right = n; while(left<=right){ int mid = left + (right - left)/2; if(isBadVersion(mid)){ right = mid - 1; }else{ left = mid + 1; } } return left; } }
你在和朋友一起玩 猜数字(Bulls and Cows)游戏,该游戏规则如下:
写出一个秘密数字,并请朋友猜这个数字是多少。朋友每猜测一次,你就会给他一个包含下述信息的提示:
猜测数字中有多少位属于数字和确切位置都猜对了(称为 “Bulls”,公牛),
有多少位属于数字猜对了但是位置不对(称为 “Cows”,奶牛)。也就是说,这次猜测中有多少位非公牛数字可以通过重新排列转换成公牛数字。
给你一个秘密数字 secret 和朋友猜测的数字 guess ,请你返回对朋友这次猜测的提示。
提示的格式为 “xAyB” ,x 是公牛个数, y 是奶牛个数,A 表示公牛,B 表示奶牛。
请注意秘密数字和朋友猜测的数字都可能含有重复数字。
思路
新建一个大小为10的数组 来计算出奶牛和公牛的数量。最后我们在一个个比较得到公牛的数量,得到答案。
代码
class Solution { public String getHint(String secret, String guess) { int s = secret.length(); int[] scount = new int[10]; for(int i=0; i < s; i++){ scount[Character.getNumericValue(secret.charAt(i))]++; scount[Character.getNumericValue(guess.charAt(i))]--; } int step = 0; for(int i=0; i < 10; i++){ if(scount[i]<0){ step += scount[i]; } } // 奶牛个数 s+step int bull = 0; for(int i=0; i<s; i++){ if(secret.charAt(i) == guess.charAt(i)) { bull++; } } int sbull = s + step - bull; return bull + "A" + sbull +"B"; } }
给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。
第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。
请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。
你必须在 O(1) 的额外空间复杂度和 O(n) 的时间复杂度下解决这个问题。
思路
确定好奇数和偶数头尾指针,我们分别进行操作。
代码
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode oddEvenList(ListNode head) { if (head == null || head.next == null) { return head; } // 奇链表尾节点 ListNode l = head; // 偶链表头结点 ListNode r = head.next; // 偶链表尾节点 ListNode e = r; while (l.next != null && e.next != null) { l.next = e.next; l = l.next; e.next = l.next; e = e.next; } l.next = r; return head; } }
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
进阶:
如果有大量输入的 S,称作 S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
思路
双指针思路,一个指针j指向s,另一个指针i指向t,遍历移动t,但s[j]等于t[i]时,移动j,即j++。得出结果。
代码
class Solution { public boolean isSubsequence(String s, String t) { int j = 0; if(s.length()==0){ return true; } if(t.length()==0){ return false; } for(int i = 0;i<t.length();i++){ if(t.charAt(i)==s.charAt(j)){ j++; } if(j == s.length()){ return true; } } return false; } }
给定一个包含大写字母和小写字母的字符串 s ,返回 通过这些字母构造成的 最长的回文串 。
在构造过程中,请注意 区分大小写 。比如 “Aa” 不能当做一个回文字符串。
思路
可以使用哈希表,也可以新建一个128的数组。对此进行计数。考虑到偶数一定可以组成回文。我们可以巧用先除二在乘二来得到偶数,注意,除此以外我们还需要加入一次单个字母(奇数,如果有的话)
代码
class Solution { public int longestPalindrome(String s) { int[] count = new int[128]; for(int i=0;i<s.length();i++){ char c = s.charAt(i); count[c]++; } int ans = 0; for(int j:count){ ans += j/2*2; if(j%2==1 && ans%2==0){ ans++; } } return ans; } }
给你一个字符串 s 和一个整数 k 。你可以选择字符串中的任一字符,并将其更改为任何其他大写英文字符。该操作最多可执行 k 次。
在执行上述操作后,返回包含相同字母的最长子字符串的长度。
思路
使用滑动窗口思想,我们使用left和right双指针,不断移动,另我们适用一个int型的historyMax记录最大值,注意判断条件right-left+1 > historyMax+k,即指针最大长度是否大于历史最大值+k,如果大于那我们移动left指针。
代码
class Solution { public int characterReplacement(String s, int k) { int left = 0,right = 0; int historyMax = 0; int n = s.length(); int[] nums = new int[26]; for(right = 0; right < n ; right++){ nums[s.charAt(right) - 'A']++; historyMax = Math.max(historyMax, nums[s.charAt(right) - 'A']); if(right-left+1 > historyMax+k){ nums[s.charAt(left) - 'A']--; left++; } } return right-left; } }
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
思路
利用滑动窗口思想,设置左右指针。我们new两个数组进行计数,通过不断移动窗口,得到想到的答案。
代码
class Solution { public List<Integer> findAnagrams(String s, String p) { List<Integer> ans = new ArrayList<Integer>(); int m = s.length(); int n = p.length(); if(m < n){ return ans; } int[] count1 = new int[26]; int[] count2 = new int[26]; for(int i=0;i<n;i++) count2[p.charAt(i)-'a']++; for(int left=0,right=0;right<m;right++){ count1[s.charAt(right)-'a']++; if(right-left+1>n){ count1[s.charAt(left)-'a']--; left++; } if(Arrays.equals(count1,count2)){ ans.add(left); } } return ans; } }
斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n ,请计算 F(n) 。
思路
这题可以用递归或者for循环来解,这里使用循环,因为递归太烂大街了。
优化:事实上 我们只需要三个变量就可以了 因为用到的也只是三个变量
代码
class Solution {
public int fib(int n) {
if(n < 2){
return n;
}
int[] a = new int[n+1];
a[0] = 0;
a[1] = 1;
for(int i=2;i<=n;i++){
a[i] = a[i-1] + a[i-2];
}
return a[n];
}
}
优化代码
class Solution { public int fib(int n) { if(n < 2){ return n; } int a = 0; int b = 1; int c = 0; for(int i=2;i<=n;i++){ c = a + b; a = b; b = c; } return c; } }
给定一个 n 叉树的根节点 root ,返回 其节点值的 前序遍历 。
n 叉树 在输入中按层序遍历进行序列化表示,每组子节点由空值 null 分隔(请参见示例)。
思路
就是简单的前序遍历,我们用递归方法。考虑到返回一个数组,我们新建一个list用来添加每次的node
代码
/* // Definition for a Node. class Node { public int val; public List<Node> children; public Node() {} public Node(int _val) { val = _val; } public Node(int _val, List<Node> _children) { val = _val; children = _children; } }; */ class Solution { public List<Integer> preorder(Node root) { List<Integer> res = new ArrayList(); forList(root,res); return res; } public void forList(Node root, List<Integer> res) { if (root == null) { return; } res.add(root.val); for (Node ch : root.children) { forList(ch, res); } } }
给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表。其中每个字母表示一种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。在任何一个单位时间,CPU 可以完成一个任务,或者处于待命状态。
然而,两个 相同种类 的任务之间必须有长度为整数 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。
你需要计算完成所有任务所需要的 最短时间 。
思路
感觉像是智力题,对于这题,我们只要判断(maxl - 1) * (n + 1) + 1,如果另有等于maxl的数,那我们加一。
代码
class Solution { public int leastInterval(char[] tasks, int n) { int[] cs = new int[26]; int l = tasks.length; for(int i = 0; i < l; i++){ cs[tasks[i] - 'A']++; } Arrays.sort(cs); int maxl = cs[25]; int retl = (maxl - 1) * (n + 1) + 1; int i = 24; while (i >= 0 && cs[i] == maxl) { retl++; i--; } return Math.max(retl,l); } }
给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。
返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序。
思路
哈希表+排序
代码
class Solution { public List<String> topKFrequent(String[] words, int k) { HashMap<String,Integer> hasht = new HashMap<String,Integer>(); for(int i = 0;i < words.length; i++){ if(hasht.get(words[i]) == null){ hasht.put(words[i],1); }else{ int x = hasht.get(words[i]) + 1; hasht.put(words[i],x); } } List<String> candidates = new ArrayList<>(hasht.keySet()); // 此处为使用 lambda 写法 candidates.sort((a, b) -> { if (hasht.get(a).equals(hasht.get(b))) { return a.compareTo(b); } else { return hasht.get(b) - hasht.get(a); } }); return candidates.subList(0, k); } }
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
思路
使用二分查找 注意判断条件 我们使用两个指针left和right,mid = left + (right-left)/2,如果mid值小于target则说明目标值在右边,我们令left = mid +1(指针多移动一位,因为其mid值已经小于了)同样right=mid-1.通过使用left<=right这个while判断。
代码
class Solution { public int search(int[] nums, int target) { int left = 0; int right = nums.length - 1; while(left<=right){ int mid = left + (right-left)/2; if(nums[mid]<target){ left = mid + 1; }else if(nums[mid]>target){ right = mid - 1; }else{ return mid; } } return -1; } }
给你一个整数数组 nums ,请计算数组的 中心下标 。
数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。
如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1 。
思路
中心下标 即其中心下标左边的值等于等于右边的值
则有 左边的值2+中心下标的值 = 总的值
我们可以先求出总的值,然后遍历找到中心下标。即把中心下标当作指针,其左边的值求和2加上此指针代表的值求和,如果等于总的值,则return 此指针。如果遍历完还没有 那么返回-1
复杂度分析
内存
O
(
1
)
O(1)
O(1)
时间
O
(
n
)
O(n)
O(n)
代码
class Solution { public int pivotIndex(int[] nums) { int numsSum = 0; int sumLeft = 0; int numsLen = nums.length; for(int i=0;i<numsLen;i++){ numsSum += nums[i]; } for(int i=0;i<numsLen;i++){ if(sumLeft*2+nums[i] == numsSum){ return i; } sumLeft += nums[i]; } return -1; } }
有一幅以 m x n 的二维整数数组表示的图画 image ,其中 image[i][j] 表示该图画的像素值大小。
你也被给予三个整数 sr , sc 和 newColor 。你应该从像素 image[sr][sc] 开始对图像进行 上色填充 。
为了完成 上色工作 ,从初始像素开始,记录初始坐标的 上下左右四个方向上 像素值与初始坐标相同的相连像素点,接着再记录这四个方向上符合条件的像素点与他们对应 四个方向上 像素值与初始坐标相同的相连像素点,……,重复该过程。将所有有记录的像素点的颜色值改为 newColor 。
最后返回 经过上色渲染后的图像 。
思路
使用dfs,我们只需判断该点是不是满足要求,才进行上色。
代码
class Solution { public void dfs(int[][] image, int x, int y, int color, int newColor){ if(image[x][y] == newColor||image[x][y] != color){ return; } image[x][y] = newColor; if (x > 0) dfs(image, x-1, y, color, newColor); if (x < image.length-1) dfs(image, x+1, y, color, newColor); if (y > 0) dfs(image, x, y-1, color, newColor); if (y < image[0].length-1) dfs(image, x, y+1, color, newColor); } public int[][] floodFill(int[][] image, int sr, int sc, int color) { int oldcolor = image[sr][sc]; dfs(image, sr, sc, oldcolor, color); return image; } }
给你一个整数数组 nums ,你可以对它进行一些操作。
每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1 和 nums[i] + 1 的元素。
开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。
思路
跟打家劫舍一样,详情可以看。我们新建一个数组,用来存储每个数的收益,从0到max的值。
代码
class Solution { public int deleteAndEarn(int[] nums) { int maxnums = 0; for(int x:nums){ maxnums = Math.max(maxnums,x); } int[] dp = new int[maxnums+1]; for(int x:nums){ dp[x] += x; } int[] dp2 = new int[maxnums+1]; dp2[0] = dp[0]; dp2[1] = Math.max(dp[0],dp[1]); for(int i = 2 ;i < maxnums+1;i++){ dp2[i] = Math.max(dp2[i-1],(dp2[i-2]+dp[i])); } return dp2[maxnums]; } }
给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。
请你计算并返回达到楼梯顶部的最低花费。
思路
动态规划,跟之前爬楼梯类似,这里我们考虑到爬到某一楼梯需要加上这个楼梯本身的代价就好了。
代码
class Solution { public int minCostClimbingStairs(int[] cost) { int n = cost.length; if(n == 2){ return Math.min(cost[0],cost[1]); } int[] dp = new int[n+1]; dp[0] = cost[0]; dp[1] = cost[1]; for(int i=2;i<n;i++){ dp[i] = Math.min(dp[i-1],dp[i-2])+cost[i]; } dp[n] = Math.min(dp[n-1],dp[n-2]); return dp[n]; } }
给你一个数组 routes ,表示一系列公交线路,其中每个 routes[i] 表示一条公交线路,第 i 辆公交车将会在上面循环行驶。
例如,路线 routes[0] = [1, 5, 7] 表示第 0 辆公交车会一直按序列 1 -> 5 -> 7 -> 1 -> 5 -> 7 -> 1 -> … 这样的车站路线行驶。
现在从 source 车站出发(初始时不在公交车上),要前往 target 车站。 期间仅可乘坐公交车。
求出 最少乘坐的公交车数量 。如果不可能到达终点车站,返回 -1 。
思路
采用 站点-路线 hashmap。用集合避免重复计算(陷入死循环)使用bfs求解
代码
class Solution { public int numBusesToDestination(int[][] routes, int source, int target) { Map<Integer, Set<Integer>> cs = new HashMap<>(); // 创建站点 路线hashmap for (int i = 0; i < routes.length; i++) { for (int j = 0; j < routes[i].length; j++) { cs.computeIfAbsent(routes[i][j], k -> new HashSet<>()).add(i + 1); } } if (source == target) return 0; if (!cs.containsKey(source) || !cs.containsKey(target)) return -1; // BFS Deque<Integer> deque = new ArrayDeque<>(); Set<Integer> vis = new HashSet<>(); for (Integer s : cs.get(source)) { for (int i : routes[s - 1]) { // 集合判重 if (vis.add(i)) deque.addLast(i); } } int level = 1; while (!deque.isEmpty()) { int size = deque.size(); while (size-- > 0) { int cur = deque.pollFirst(); if (cur == target) return level; Set<Integer> s = cs.getOrDefault(cur, null); if (s == null) continue; for (Integer ss : s) { for (int i : routes[ss - 1]) { if (vis.add(i)) deque.addLast(i); } } } level++; } return -1; } }
给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。
注意:如果对空文本输入退格字符,文本继续为空。
思路
我们可以从后往前读,遇到#我们则记下来,然后进行跳过,以此比较每一个字符 如果均没有问题则返回true 否则返回false
代码
class Solution { public boolean backspaceCompare(String s, String t) { int i = s.length() - 1, j = t.length() - 1; int skipS = 0, skipT = 0; while (i >= 0 || j >= 0) { while (i >= 0) { if (s.charAt(i) == '#') { skipS++; i--; } else if (skipS > 0) { skipS--; i--; } else { break; } } while (j >= 0) { if (t.charAt(j) == '#') { skipT++; j--; } else if (skipT > 0) { skipT--; j--; } else { break; } } if (i >= 0 && j >= 0) { if (s.charAt(i) != t.charAt(j)) { return false; } } else { if (i >= 0 || j >= 0) { return false; } } i--; j--; } return true; } }
给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
思路
先遍历计算链表的长度,然后遍历到长度的一半输出其值。
代码
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode middleNode(ListNode head) { int listLength = 0; ListNode cur = head; while(cur != null){ listLength++; cur = cur.next; } for(int i = 0; i<listLength/2; i++){ head = head.next; } return head; } }
如果数组是单调递增或单调递减的,那么它是 单调 的。
如果对于所有 i <= j,nums[i] <= nums[j],那么数组 nums 是单调递增的。 如果对于所有 i <= j,nums[i]> = nums[j],那么数组 nums 是单调递减的。
当给定的数组 nums 是单调数组时返回 true,否则返回 false。
思路
根据判断nums[0] <= nums[nums.length-1]确定是否单调递增或者递减,然后进行遍历判断。
代码
class Solution { public boolean isMonotonic(int[] nums) { if(nums.length == 1) return true; if(nums[0] <= nums[nums.length-1]){ for(int i = 1; i < nums.length; i++){ if(nums[i] < nums[i-1]){ return false; } } }else{ for(int i = 1; i < nums.length; i++){ if(nums[i] > nums[i-1]){ return false; } } } return true; } }
给定一个长度为 n 的环形整数数组 nums ,返回 nums 的非空 子数组 的最大可能和 。
环形数组 意味着数组的末端将会与开头相连呈环状。形式上, nums[i] 的下一个元素是 nums[(i + 1) % n] , nums[i] 的前一个元素是 nums[(i - 1 + n) % n] 。
子数组 最多只能包含固定缓冲区 nums 中的每个元素一次。形式上,对于子数组 nums[i], nums[i + 1], …, nums[j] ,不存在 i <= k1, k2 <= j 其中 k1 % n == k2 % n 。
思路
(没做出来 思路借鉴别别人的)
跟之前最大子数组和类似,在这里我们考虑到环状形状,我们只需要在求一个最小数组和就好了 我们只需要考虑总的值减去最小数组和和最大数组和的较大值。为什么?
证明一下最大子数组是环形的情况
max(前缀数组+后缀数组)
= max(数组总和 - subarray) subarray指的是前缀数组和后缀数组中间的数组
= 数组总和 + max(-subarray) 数组总和是不变的,直接提出来
= 数组总和 - min(subarry) 。。。这个都懂吧,把负号提出来,max变成min
极端情况:如果说这数组的所有数都是负数,那么上面的公式还需要变一下,因为这种情况,对于上面的第一种情况sum会等于数组中的最大值,而对二种情况sum=0(最小的子数组就是本数组,total-total=0)。所以多加一个case,判断最大子数组和是否小于0,小于0,直接返回该maxSubArray
代码
class Solution {
public int maxSubarraySumCircular(int[] nums) {
int fn = -1;
int res = Integer.MIN_VALUE;
int total = 0, minSum = nums[0], curMin = 0;
for(int a:nums){
fn = Math.max(fn+a,a);
res = Math.max(fn,res);
curMin = Math.min(curMin + a, a);
minSum = Math.min(minSum, curMin);
total += a;
}
return res > 0 ? Math.max(res, total - minSum) : res;
}
}
给你一个二元数组 nums ,和一个整数 goal ,请你统计并返回有多少个和为 goal 的 非空 子数组。
子数组 是数组的一段连续部分。
思路
注意连续,我们使用滑动窗口的思想,得出如所求
代码
class Solution { public int numSubarraysWithSum(int[] nums, int goal) { int count = 0, sum = 0; for(int i = 0; i < nums.length; i++){ for(int j = i; j< nums.length; j++){ sum += nums[j]; if(sum == goal){ count++; } else if(sum > goal){ break; } } sum = 0; } return count; } }
在给定的 m x n 网格 grid 中,每个单元格可以有以下三个值之一:
值 0 代表空单元格;
值 1 代表新鲜橘子;
值 2 代表腐烂的橘子。
每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。
返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1 。
思路
使用bfs,再采用一个变量记橘子的个数就行了 。
代码
class Solution { public int orangesRotting(int[][] grid) { int m = grid.length,n = grid[0].length; Queue<Integer> queue = new LinkedList<>(); int countOrange = 0; for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ if(grid[i][j] == 2){ queue.add(i * n + j); }else if(grid[i][j] == 1){ countOrange++; } } } int time = 0; while( !queue.isEmpty() && countOrange > 0 ){ time++; int size = queue.size(); for (int i = 0; i < size; i++){ int p = queue.poll(); int x = p / n, y = p % n; if (x - 1 >= 0 && grid[x - 1][y] == 1) { // 上 countOrange--; grid[x - 1][y] = 2; queue.offer((x - 1) * n + y); } if (x + 1 < m && grid[x + 1][y] == 1) { // 下 countOrange--; grid[x + 1][y] = 2; queue.offer((x + 1) * n + y); } if (y - 1 >= 0 && grid[x][y - 1] == 1) { // 左 countOrange--; grid[x][y - 1] = 2; queue.offer(x * n + y - 1); } if (y + 1 < n && grid[x][y + 1] == 1) { // 右 countOrange--; grid[x][y + 1] = 2; queue.offer(x * n + y + 1); } } } if(countOrange > 0){ return -1; }else{ return time; } } }
给定正整数 n,返回在 [1, n] 范围内具有 至少 1 位 重复数字的正整数的个数。
思路
涉及到排列组合。首先,要求至少有一位重复的数字,这个比较难实现,所以我们可以考虑求它的 反命题 ,即 没有字符是重复的 ,然后再拿总的情况减去反命题,就得到了我们要求的命题的结果。那么现在问题转化为求没有字符是重复的,实际上就是求解一个排列组合问题。
假设我们有一个 N=8765,那么有以下几种情况进行分类讨论:
数字的位数小于 N 的位数,假设为三位数 XXX,那么如何求解三位数中没有重复数字的个数呢?
显然这是个排列组合问题,最高位的数字从 1-9 中选择一个数字填入,则为 C(9, 1)。原先个位数和十位数共可以选择十个数字(0-9),但是因为百位数已经选了一个数字了,所以就只剩下 9 个数字可选,那么就共有 A(9, 2)种情况,结合乘法原理,三位数的情况下共有 C(9, 1) * A(9, 2) 种情况。两位数和一位数同理。然后考虑特殊的情况
代码
class Solution { public int numDupDigitsAtMostN(int N) { // Transform N + 1 to arrayList ArrayList<Integer> L = new ArrayList<Integer>(); for (int x = N + 1; x > 0; x /= 10) L.add(0, x % 10); // Count the number with digits < N int res = 0, n = L.size(); for (int i = 1; i < n; ++i) res += 9 * fn(9, i - 1); // Count the number with same prefix HashSet<Integer> seen = new HashSet<>(); for (int i = 0; i < n; ++i) { for (int j = i > 0 ? 0 : 1; j < L.get(i); ++j) if (!seen.contains(j)) res += fn(9 - i, n - i - 1); if (seen.contains(L.get(i))) break; seen.add(L.get(i)); } return N - res; } public int fn(int m, int n) { return n == 0 ? 1 : fn(m, n - 1) * (m - n + 1); } }
给你一个正整数数组 values,其中 values[i] 表示第 i 个观光景点的评分,并且两个景点 i 和 j 之间的 距离 为 j - i。
一对景点(i < j)组成的观光组合的得分为 values[i] + values[j] + i - j ,也就是景点的评分之和 减去 它们两者之间的距离。
返回一对观光景点能取得的最高分。
思路
跟买卖彩票类似 ,这里我们添加或减去j值即可
代码
class Solution {
public int maxScoreSightseeingPair(int[] values) {
int left = values[0], res = Integer.MIN_VALUE;
for (int j = 1; j < values.length; j++) {
res = Math.max(res, left + values[j] - j);
left = Math.max(left, values[j] + j);
}
return res;
}
}
给定一个长度为 n 的链表 head
对于列表中的每个节点,查找下一个 更大节点 的值。也就是说,对于每个节点,找到它旁边的第一个节点的值,这个节点的值 严格大于 它的值。
返回一个整数数组 answer ,其中 answer[i] 是第 i 个节点( 从1开始 )的下一个更大的节点的值。如果第 i 个节点没有下一个更大的节点,设置 answer[i] = 0 。
思路
一开始就想到遍历,双指针,一个指向当前结点,另一个开始遍历,直到数值比当前结点大。时间复杂度为n2。后面采用从后面开始,当i+1比i大,那么我们进行更改。并存储i+1的值(后面可能能用到)。可惜时间也比较大,到时候在想办法改进吧。
代码
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public int[] nextLargerNodes(ListNode head) { ArrayList<Integer> nodelist = new ArrayList<>(); while(head != null){ nodelist.add(head.val); head = head.next; } int[] res = new int[nodelist.size()]; ArrayList<Integer> maxint = new ArrayList<>(); maxint.add(-1); res[nodelist.size()-1] = 0; for(int i = nodelist.size() -2; i >= 0; i--){ if(nodelist.get(i+1) > nodelist.get(i)){ res[i] = nodelist.get(i+1); maxint.add(nodelist.get(i+1)); }else if(Collections.max(maxint) > nodelist.get(i)){ int j = maxint.size(); while(true){ if(maxint.get(j-1) > nodelist.get(i)){ res[i] = maxint.get(j-1); break; } j--; } }else{ res[i] = 0; } } return res; } }
有一堆石头,每块石头的重量都是正整数。
每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0。
思路
很简单,第一想到的是排序做,但其实这样的复杂度为n2logn,因为你每次都要排序(虽然后面是排序一个值就好了 但是算法复杂度也是n),后面觉得最大堆更好(但是我没用)
代码
class Solution {
public int lastStoneWeight(int[] stones) {
Arrays.sort(stones);
int len = stones.length - 1;
for (int i = len; i > 0; i--) {
stones[len] = stones[len] - stones[len - 1];
stones[len - 1] = 0;
Arrays.sort(stones);
}
return stones[len];
}
}
给你一个正整数数组 arr(可能存在重复的元素),请你返回可在 一次交换(交换两数字 arr[i] 和 arr[j] 的位置)后得到的、按字典序排列小于 arr 的最大排列。
如果无法这么操作,就请返回原数组。
思路
代码
class Solution { public int[] prevPermOpt1(int[] arr) { int min = arr[arr.length - 1]; int i = arr.length - 2; for(; i >= 0; i--) { if(arr[i] > min) { break; } min = Math.min(min, arr[i]); } int idx = -1, max = 0; for(int j = i + 1; i >= 0 && j < arr.length; j++) { if(arr[j] < arr[i] && arr[j] > max) { max = arr[j]; idx = j; } } if(idx != -1) { arr[idx] = arr[i]; arr[i] = max; } return arr; } }
泰波那契序列 Tn 定义如下:
T0 = 0, T1 = 1, T2 = 1, 且在 n >= 0 的条件下 Tn+3 = Tn + Tn+1 + Tn+2
给你整数 n,请返回第 n 个泰波那契数 Tn 的值。
思路
同斐波那契数,这里使用四个变量
代码
class Solution { public int tribonacci(int n) { int a = 0,b = 1,c = 1,d = 0; if(n == 0){ return 0; } if(n <= 2){ return 1; } for(int i = 3;i <= n;i++){ d = a + b + c; a = b; b = c; c = d; } return d; } }
给你一个正整数数组 nums,你需要从中任选一些子集,然后将子集中每一个数乘以一个 任意整数,并求出他们的和。
假如该和结果为 1,那么原数组就是一个「好数组」,则返回 True;否则请返回 False。
思路
求最大公约数是否为1.
裴蜀定理:
若a,b是整数,且gcd(a,b)=d,那么对于任意的整数x,y,ax+by都一定是d的倍数,
特别地,一定存在整数x,y,使ax+by=d成立。
a,b互质的充要条件是存在整数x,y使ax+by=1.
代码
class Solution { public boolean isGoodArray(int[] nums) { int div = nums[0]; for(int num:nums){ div = gcd(div, num); if(div == 1){ return div == 1; } } return div == 1; } public int gcd(int num1,int num2){ while (num2 != 0) { int temp = num1; num1 = num2; num2 = temp % num2; } return num1; } }
给你一个数组 nums 。数组「动态和」的计算公式为:runningSum[i] = sum(nums[0]…nums[i]) 。
请返回 nums 的动态和。
思路
这题我们遍历就好了 使用nums[n] += nums[n-1]求出和(即前缀和)
复杂度分析
内存
O
(
1
)
O(1)
O(1)
时间
O
(
n
)
O(n)
O(n)
代码
class Solution {
public int[] runningSum(int[] nums) {
if(nums.length == 1){
return nums;
}
for(int i=1;i<nums.length;i++){
nums[i] = nums[i] + nums[i-1];
}
return nums;
}
}
给你一个长度为 n 的字符串数组 names 。你将会在文件系统中创建 n 个文件夹:在第 i 分钟,新建名为 names[i] 的文件夹。
由于两个文件 不能 共享相同的文件名,因此如果新建文件夹使用的文件名已经被占用,系统会以 (k) 的形式为新文件夹的文件名添加后缀,其中 k 是能保证文件名唯一的 最小正整数 。
返回长度为 n 的字符串数组,其中 ans[i] 是创建第 i 个文件夹时系统分配给该文件夹的实际名称。
思路
一开始用哈希表和集合,发现超时了,考虑到如何减少用时,我们只能用哈希表,哈希表中的值用来存储出现的次数,避免重复运算。
代码
class Solution { public String[] getFolderNames(String[] names) { HashMap<String, Integer> index = new HashMap<>(); for (int i = 0; i < names.length; i++) { String name = names[i]; if (!index.containsKey(name)) { names[i] = name; index.put(name, 1); } else { int k = index.get(name); while (index.containsKey(name + "(" + k + ")")) { k++; } names[i] = name + "(" + k + ")"; index.put(name, k + 1); index.put(names[i], 1); } } return names; } }
给你一个二维字符网格数组 grid ,大小为 m x n ,你需要检查 grid 中是否存在 相同值 形成的环。
一个环是一条开始和结束于同一个格子的长度 大于等于 4 的路径。对于一个给定的格子,你可以移动到它上、下、左、右四个方向相邻的格子之一,可以移动的前提是这两个格子有 相同的值 。
同时,你也不能回到上一次移动时所在的格子。比方说,环 (1, 1) -> (1, 2) -> (1, 1) 是不合法的,因为从 (1, 2) 移动到 (1, 1) 回到了上一次移动时的格子。
如果 grid 中有相同值形成的环,请你返回 true ,否则返回 false 。
思路
对于这道题我一开始考虑的是dfs,然后我们标记一下以及访问的点,如果重新回到访问的点。那我们判断为true,否则为false。我们遍历整个数组得到所求。但是!超时了。然后只能借鉴官方的做法–基于并查集的判断方法:从左上角顶点开始,同时向右和向下搜索,若字母相同则合并。合并时,若发现 x 和 y 的 parent 相同,即形成环。
代码
dfs求解
class Solution { public boolean containsCycle(char[][] grid) { for(int i = 0; i < grid.length; i++){ for(int j = 0; j < grid[0].length; j++){ if(dfs(grid,grid[i][j],i,j,0)){ return true; } } } return false; // 0 右 1 下 2 左 3 上 } public boolean dfs(char[][] grid, char ch, int x, int y, int f){ if(x < 0 || y < 0 || x >= grid.length || y >= grid[0].length) return false; // 查看 当前字母与初始字母是否相同 if(grid[x][y] != ch) return (char)(ch - 32) == grid[x][y]; // 将字符转换为大写表示这个点来过 grid[x][y] = (char)(ch - 32); return (f != 2 && dfs(grid,ch,x+1,y,0)) || (f != 3 && dfs(grid,ch,x,y+1,1)) || (f != 0 && dfs(grid,ch,x-1,y,2)) || (f != 1 && dfs(grid,ch,x,y-1,3)); } }
并查集
class Solution { public boolean containsCycle(char[][] grid) { int h = grid.length; int w = grid[0].length; DSU dsu = new DSU(w * h); for (int i = 0; i < h; ++i) { for (int j = 0; j < w; ++j) { char cur = grid[i][j]; // 向右搜索 if (j + 1 < w && cur == grid[i][j + 1]) { if (dsu.union(i * w + j, i * w + j + 1)) { return true; } } // 向下搜索 if (i + 1 < h && cur == grid[i + 1][j]) { if (dsu.union(i * w + j, (i + 1) * w + j)) { return true; } } } } return false; } class DSU { int[] parent; public DSU(int N) { parent = new int[N]; for (int i = 0; i < N; ++i) { parent[i] = i; } } public int find(int x) { if (parent[x] != x) { parent[x] = find(parent[x]); } return parent[x]; } public boolean union(int x, int y) { int parentX = find(x); int parentY = find(y); if (parentX == parentY) { return true; } if (parentX < parentY) { parent[parentY] = parentX; } else { parent[parentX] = parentY; } return false; } } }
给你一个整数数组 nums ,请你求出乘积为正数的最长子数组的长度。
一个数组的子数组是由原数组中零个或者更多个连续数字组成的数组。
请你返回乘积为正数的最长子数组长度。
思路
我们可以使用0来分隔(因为必须要正数),然后我们分别统计每个子数组负数出现的次数以及最小舍去代价,如果我们次数不为偶数,那我们要舍去最小舍去代价的值 来得到子数组的正数长度,最后求最大长度。
代码
class Solution { public int getMaxLen(int[] nums) { int pos = 0; int res = 0; for(int i=0; i<nums.length; i++){ if(nums[i] == 0){ res = Math.max(res, maxlen(nums, pos, i)); pos = i+1; } } res = Math.max(res,maxlen(nums, pos, nums.length)); return res; } public int maxlen(int[] nums,int pos,int i){ int ns = 0; int posns = Integer.MAX_VALUE; for(int j=pos;j<i;j++){ if(nums[j] < 0){ ns++; posns = Math.min(Math.min(j-pos+1,posns),i-j); } } if(ns%2 == 0){ return i-pos; }else{ return i-pos-posns; } } }
给你两个非负整数数组 rowSum 和 colSum ,其中 rowSum[i] 是二维矩阵中第 i 行元素的和, colSum[j] 是第 j 列元素的和。换言之你不知道矩阵里的每个元素,但是你知道每一行和每一列的和。
请找到大小为 rowSum.length x colSum.length 的任意 非负整数 矩阵,且该矩阵满足 rowSum 和 colSum 的要求。
请你返回任意一个满足题目要求的二维矩阵,题目保证存在 至少一个 可行矩阵。
思路
因为满足其中的条件的矩阵即可 那么每次求每个点的行列的min
代码
class Solution {
public int[][] restoreMatrix(int[] rowSum, int[] colSum) {
int[][] res = new int[rowSum.length][colSum.length];
for (int i = 0; i < rowSum.length; i++) {
for (int j = 0; j < colSum.length; j++) {
res[i][j] = Math.min(rowSum[i], colSum[j]);
rowSum[i] -= res[i][j];
colSum[j] -= res[i][j];
}
}
return res;
}
}
假设你是球队的经理。对于即将到来的锦标赛,你想组合一支总体得分最高的球队。球队的得分是球队中所有球员的分数 总和 。
然而,球队中的矛盾会限制球员的发挥,所以必须选出一支 没有矛盾 的球队。如果一名年龄较小球员的分数 严格大于 一名年龄较大的球员,则存在矛盾。同龄球员之间不会发生矛盾。
给你两个列表 scores 和 ages,其中每组 scores[i] 和 ages[i] 表示第 i 名球员的分数和年龄。请你返回 所有可能的无矛盾球队中得分最高那支的分数 。
思路
动态规划题,先对数组进行排序,先按年龄排,再按分数排(从小到大),设置动态方程组 dp[i] = Math.max(dp[i], combine[i][1] + dp[j])。
代码
class Solution { public int bestTeamScore(int[] scores, int[] ages) { int len = scores.length; int[][] combine = new int[len][2]; for(int i = 0; i < len; i++){ combine[i][0] = ages[i]; combine[i][1] = scores[i]; } Arrays.sort(combine, (o1, o2) -> { return o1[0] != o2[0] ? o1[0] - o2[0] : o1[1] - o2[1]; }); int[] dp = new int[len]; int ans = 0; for(int i = 0; i < len; i++){ dp[i] = combine[i][1]; for(int j = 0; j < i; j++){ if(combine[j][1] <= combine[i][1]){ dp[i] = Math.max(dp[i], combine[i][1] + dp[j]); } } } for(int i = 0; i < len; i++){ ans = Math.max(ans, dp[i]); } return ans; } }
用一个大小为 m x n 的二维网格 grid 表示一个箱子。你有 n 颗球。箱子的顶部和底部都是开着的。
箱子中的每个单元格都有一个对角线挡板,跨过单元格的两个角,可以将球导向左侧或者右侧。
将球导向右侧的挡板跨过左上角和右下角,在网格中用 1 表示。
将球导向左侧的挡板跨过右上角和左下角,在网格中用 -1 表示。
在箱子每一列的顶端各放一颗球。每颗球都可能卡在箱子里或从底部掉出来。如果球恰好卡在两块挡板之间的 “V” 形图案,或者被一块挡导向到箱子的任意一侧边上,就会卡住。
返回一个大小为 n 的数组 answer ,其中 answer[i] 是球放在顶部的第 i 列后从底部掉出来的那一列对应的下标,如果球卡在盒子里,则返回 -1 。
思路
这题看起来挺麻烦的,但是做起来然后很容易的,我们首先要判断怎么样才能卡住,要么就是在边界,要么就是两个相邻的不相等。那我们可以进行模拟,设置r,c值,r值不断增加,如果grid当前值唯一 则c+1 否则c-1
代码
class Solution { int[][] g; int m; int n; public int[] findBall(int[][] grid) { g = grid; m = grid.length; n = grid[0].length; int[] res = new int[n]; for(int i = 0; i < n; i++){ res[i] = helpFindBall(i); } return res; } public int helpFindBall(int i){ int c = 0, r = i; while(c < m){ int temp = g[c][r] + r; if(temp < 0||temp >= n) return -1; if(g[c][r] != g[c][temp]) return -1; r = temp; c++; } return r; } }
给你一个字符串数组 words 。words 中每个元素都是一个包含 两个 小写英文字母的单词。
请你从 words 中选择一些元素并按 任意顺序 连接它们,并得到一个 尽可能长的回文串 。每个元素 至多 只能使用一次。
请你返回你能得到的最长回文串的 长度 。如果没办法得到任何一个回文串,请你返回 0 。
回文串 指的是从前往后和从后往前读一样的字符串。
思路
之前就想到哈希表来做,我们只需要判断他的倒着的存不存在,如果存在边+4,如果不存在则不管。除此之外需要考虑自身是否回文(只需要考虑一个即可)。这个对于不仅仅两个也适用,考虑到优化,我们使用一个26*26的数组即可
代码
class Solution { public int longestPalindrome(String[] words) { char[][] ch = new char[26][26]; int c1, c2; int l = 0; for(String w : words) { c1 = w.charAt(0) - 'a'; c2 = w.charAt(1) - 'a'; if(ch[c2][c1] > 0) { ch[c2][c1]--; l += 4; continue; } ch[c1][c2]++; } for(int i = 0; i < 26; i++) { if(ch[i][i] > 0) { l += 2; break; } } return l; } }
给你一个下标从 0 开始的整数数组 nums 。在一步操作中,你可以执行以下步骤:
从 nums 选出 两个 相等的 整数
从 nums 中移除这两个整数,形成一个 数对
请你在 nums 上多次执行此操作直到无法继续执行。
返回一个下标从 0 开始、长度为 2 的整数数组 answer 作为答案,其中 answer[0] 是形成的数对数目,answer[1] 是对 nums 尽可能执行上述操作后剩下的整数数目。
思路
我们只需要新建一个数组进行计数即可,每次判断,如果是偶数则为0。然后输出所求。
代码
class Solution { public int[] numberOfPairs(int[] nums) { int [] ch = new int[101]; for(int i = 0; i < nums.length; i++){ ch[nums[i]]++; if(ch[nums[i]] % 2 == 0){ ch[nums[i]] = 0; } } int sumch = 0; for(int j = 0; j < 101; j++){ sumch += ch[j]; } return new int[]{(nums.length-sumch)/2,sumch}; } }
给你一个长度为 n 下标从 0 开始的字符串 blocks ,blocks[i] 要么是 ‘W’ 要么是 ‘B’ ,表示第 i 块的颜色。字符 ‘W’ 和 ‘B’ 分别表示白色和黑色。
给你一个整数 k ,表示想要 连续 黑色块的数目。
每一次操作中,你可以选择一个白色块将它 涂成 黑色块。
请你返回至少出现 一次 连续 k 个黑色块的 最少 操作次数。
思路
设计一个大小为7的左右指针用来计数七个中最大的黑块数量即可
代码
class Solution { public int minimumRecolors(String blocks, int k) { // 记录黑白块个数 int b = 0,w = 0; for(int i = 0; i < k; i++){ if(blocks.charAt(i) == 'B'){ b++; }else{ w++; } } int res = b; int fn = 0; for(int left = 0, right = k;right < blocks.length();right++,left++){ if(blocks.charAt(left) == 'B'){ b--; }else{ w--; } if(blocks.charAt(right) == 'B'){ b++; }else{ w++; } fn = b; res = Math.max(res, fn); } return k - res; } }
你正在参加一场比赛,给你两个 正 整数 initialEnergy 和 initialExperience 分别表示你的初始精力和初始经验。
另给你两个下标从 0 开始的整数数组 energy 和 experience,长度均为 n 。
你将会 依次 对上 n 个对手。第 i 个对手的精力和经验分别用 energy[i] 和 experience[i] 表示。当你对上对手时,需要在经验和精力上都 严格 超过对手才能击败他们,然后在可能的情况下继续对上下一个对手。
击败第 i 个对手会使你的经验 增加 experience[i],但会将你的精力 减少 energy[i] 。
在开始比赛前,你可以训练几个小时。每训练一个小时,你可以选择将增加经验增加 1 或者 将精力增加 1 。
返回击败全部 n 个对手需要训练的 最少 小时数目。
思路
精力用求和即可求出,经验可以用前缀和的方式,用该数减去它的前缀和求最大值。
代码
class Solution { public int minNumberOfHours(int initialEnergy, int initialExperience, int[] energy, int[] experience) { int m = 0,n = 0,res; for(int i = 0; i < energy.length; i++){ m += energy[i]; } if(initialEnergy > m){ res = 0; }else{ res = m - initialEnergy + 1; } for(int j = 1; j < experience.length; j++){ int temp = experience[j]; experience[j] += experience[j-1]; if( n < temp-experience[j-1]){ n = temp-experience[j-1]; } } n = Math.max(n,experience[0]); if(initialExperience <= n){ res += n - initialExperience + 1; } return res; } }
给你一个长度为 n 的整数数组 nums ,和一个长度为 m 的整数数组 queries 。
返回一个长度为 m 的数组 answer ,其中 answer[i] 是 nums 中 元素之和小于等于 queries[i] 的 子序列 的 最大 长度 。
子序列 是由一个数组删除某些元素(也可以不删除)但不改变剩余元素顺序得到的一个数组。
思路
先排序然后使用前缀和。
代码
class Solution { public int[] answerQueries(int[] nums, int[] queries) { // 排序 Arrays.sort(nums); // 求前缀和 for(int i = 1; i < nums.length; i++){ nums[i] += nums[i-1]; } for(int i = 0; i < queries.length; i++){ for(int j = 0; j < nums.length; j++){ if(nums[j] > queries[i]){ queries[i] = j; break; } } if(nums[nums.length-1] <= queries[i]){ queries[i] = nums.length; } } return queries; } }
给你一个四舍五入到两位小数的非负浮点数 celsius 来表示温度,以 摄氏度(Celsius)为单位。
你需要将摄氏度转换为 开氏度(Kelvin)和 华氏度(Fahrenheit),并以数组 ans = [kelvin, fahrenheit] 的形式返回结果。
返回数组 ans 。与实际答案误差不超过 10-5 的会视为正确答案。
注意:
开氏度 = 摄氏度 + 273.15
华氏度 = 摄氏度 * 1.80 + 32.00
思路
太简单了
代码
class Solution {
public double[] convertTemperature(double celsius) {
return new double[]{celsius + 273.15,celsius*1.80 + 32.00};
}
}
在 「力扣挑战赛」 开幕式的压轴节目 「无人机方阵」中,每一架无人机展示一种灯光颜色。 无人机方阵通过两种操作进行颜色图案变换:
调整无人机的位置布局
切换无人机展示的灯光颜色
给定两个大小均为 N*M 的二维数组 source 和 target 表示无人机方阵表演的两种颜色图案,由于无人机切换灯光颜色的耗能很大,请返回从 source 到 target 最少需要多少架无人机切换灯光颜色。
注意: 调整无人机的位置布局时无人机的位置可以随意变动。
思路
建立一个数组进行计数即可。
代码
class Solution { public int minimumSwitchingTimes(int[][] source, int[][] target) { int[] s = new int[10001]; for(int i=0; i<source.length; i++){ for(int j=0;j<source[0].length; j++){ s[source[i][j]]++; } } for(int i=0; i<source.length; i++){ for(int j=0;j<source[0].length; j++){ s[target[i][j]]--; } } int sumk = 0; for(int k=0;k<10001;k++){ if(s[k]<=0){ sumk -= s[k]; } } return sumk; } }
实现一个算法,确定一个字符串 s 的所有字符是否全都不同。
思路
考虑到尽量不使用额外的数据结构,我们直接用位运算(其实用于一个26的数组会很容易就解出来了)
代码
class Solution {
public boolean isUnique(String astr) {
int mask = 0,step = 0;
for(int i = 0; i<astr.length(); i++){
step = astr.charAt(i) - 'a';
if( (mask & (1<<step)) != 0){
return false;
}
mask = mask | (1<<step);
}
return true;
}
}
给定两个由小写字母组成的字符串 s1 和 s2,请编写一个程序,确定其中一个字符串的字符重新排列后,能否变成另一个字符串。
思路
用一个大小为26的数组用来计数即可
代码
class Solution { public boolean CheckPermutation(String s1, String s2) { if(s1.length() != s2.length()){ return false; } int[] schar = new int[26]; for(int i = 0; i < s1.length(); i++){ schar[s1.charAt(i)-'a']++; schar[s2.charAt(i)-'a']--; } for(int j = 0; j < 26; j++){ if(schar[j] != 0){ return false; } } return true; } }
URL化。编写一种方法,将字符串中的空格全部替换为%20。假定该字符串尾部有足够的空间存放新增字符,并且知道字符串的“真实”长度。(注:用Java实现的话,请使用字符数组实现,以便直接在数组上操作。)
思路
先计算空格数M 在生成一个 原始长度+M*2的char类型数组(这里考虑到“ ”变为%20)然后转化为字符串
代码
class Solution { public String replaceSpaces(String S, int length) { char[] s = S.toCharArray(); int M = 0; int j = 0; int N = length; for (int i = 0; i < N; i++) { if (s[i] == ' '){ M ++; } } M = M * 2 + N; char[] arr = new char[M]; for (int i = 0; i < N; i++) { if (s[i] == ' '){ arr[j++] = '%'; arr[j++] = '2'; arr[j++] = '0'; }else{ arr[j++] = s[i]; } } return new String(arr); } }
给定一个字符串,编写一个函数判定其是否为某个回文串的排列之一。
回文串是指正反两个方向都一样的单词或短语。排列是指字母的重新排列。
回文串不一定是字典当中的单词。
思路
利用哈希表,考虑到字母里面不一定为小写字母 会存在其他字母 所以用哈希表比较方便。(如果全部是小写字母或者大写字母可以考虑用位运算)
代码
class Solution { public boolean canPermutePalindrome(String s) { HashMap<Character, Integer> cs = new HashMap<>(); for(int i = 0; i < s.length(); i++){ char name = s.charAt(i); if(!cs.containsKey(name)){ cs.put(name, 1); }else{ int k = cs.get(name); cs.put(name, k + 1); } } int flag = 0; for (Integer value : cs.values()) { if(value % 2 == 1) { flag++; if(flag > 1){ return false; } } } return true; } }
字符串有三种编辑操作:插入一个英文字符、删除一个英文字符或者替换一个英文字符。 给定两个字符串,编写一个函数判定它们是否只需要一次(或者零次)编辑。
思路
进行观察,已知至多操作一次,那么我们可以先比较长度,如果超过1那么直接false,考虑到等于1的情况,逐个比较如果不相等,根据是1还是-1进行删除或者增加,这里考虑到如果是(可能最小次数是替换,但是如果替换,则后续也得删除或者增加 操作已经超过1了 所以不用在意,我们并不是要求最小操作次数)
代码
class Solution { public boolean oneEditAway(String first, String second) { int len = first.length() - second.length(); if(len > 1 || len < -1){ return false; } int count = 0; for(int i = 0,j = 0; i < first.length() && j < second.length(); i++,j++){ if(first.charAt(i) != second.charAt(j)){ if(len == 1){ j--; } else if(len == -1){ i--; } count++; } if(count > 1) { return false; } } return true; } }
字符串压缩。利用字符重复出现的次数,编写一种方法,实现基本的字符串压缩功能。比如,字符串aabcccccaaa会变为a2b1c5a3。若“压缩”后的字符串没有变短,则返回原先的字符串。你可以假设字符串中只包含大小写英文字母(a至z)。
思路
快慢指针,快指针-慢指针用来计数,慢指针用来存该字符。建立一个StringBuilder用来存储。
代码
class Solution { public String compressString(String S) { if(S.length() == 0) return S; char[] chars = S.toCharArray(); StringBuilder sb = new StringBuilder(); int slow = 0, fast = 0; while(fast <= chars.length){ if(fast == chars.length){ sb.append(chars[slow]).append(fast-slow); }else if(chars[fast] != chars[slow]){ sb.append(chars[slow]).append(fast-slow); slow = fast; } fast++; } return sb.length() < chars.length ? sb.toString() : S; } }
给你一幅由 N × N 矩阵表示的图像,其中每个像素的大小为 4 字节。请你设计一种算法,将图像旋转 90 度。
不占用额外内存空间能否做到?
思路
本题与48题相同,我们可以考虑先转置以后进行观察,发现我们只需要对每行进行调转就可以了。
代码
class Solution { public void rotate(int[][] matrix) { for(int i = 0; i < matrix.length; i++){ for(int j = 0; j < i; j++){ int temp = matrix[i][j]; matrix[i][j] = matrix[j][i]; matrix[j][i] = temp; } } int m = matrix[0].length; for(int i = 0; i < matrix.length; i++){ for(int j = 0; j < m/2; j++){ int temp = matrix[i][j]; matrix[i][j] = matrix[i][m-j-1]; matrix[i][m-j-1] = temp; } } } }
编写一种算法,若M × N矩阵中某个元素为0,则将其所在的行与列清零。
思路
我用一个集合用来存储0的行数和列数,然后再遍历把相应的置为0即可。事实上,我们可以用第一列和第一行用来存储是否有0,如果第一列和第一行的存在0那么我们用个常数标记一下即可。
代码
class Solution { public void setZeroes(int[][] matrix) { Set<Integer> hashSet1 = new HashSet<Integer>(); Set<Integer> hashSet2 = new HashSet<Integer>(); for(int i =0; i < matrix.length; i++){ for(int j = 0; j< matrix[0].length; j++){ if(matrix[i][j] == 0){ hashSet1.add(i); hashSet2.add(j); } } } for(int str:hashSet1){ for(int j = 0; j< matrix[0].length; j++){ matrix[str][j] = 0; } } for(int str:hashSet2){ for(int i =0; i < matrix.length; i++){ matrix[i][str] = 0; } } } }
字符串轮转。给定两个字符串s1和s2,请编写代码检查s2是否为s1旋转而成(比如,waterbottle是erbottlewat旋转后的字符串)。
思路
s2+s2 然后判断s2中是否含有s1就行了
代码
class Solution {
public boolean isFlipedString(String s1, String s2) {
if(s1.length() != s2.length()){
return false;
}
if(s1.length() == 0){
return true;
}
s2 = s2 + s2;
// 判断s2中 是否含有s1
return s2.contains(s1);
}
}
编写代码,移除未排序链表中的重复节点。保留最开始出现的节点。
思路
使用一个hashset用来判断元素是否已经出现过。(如果不适用额外的空间,可以考虑多使用一个指针进行遍历,每次经过一点,判断该点的值在后面有没有出现过,类似暴力求解。算法复杂度为n2)
代码
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode removeDuplicateNodes(ListNode head) { if(head == null){ return head; } Set<Integer> oc = new HashSet<Integer>(); oc.add(head.val); ListNode p = head; while(p.next != null){ if(oc.add(p.next.val)){ p = p.next; }else{ p.next = p.next.next; } } p.next = null; return head; } }
实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。
思路
先遍历一遍计算长度。
代码
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public int kthToLast(ListNode head, int k) { int len = 1; ListNode p = head; while(p.next != null){ p = p.next; len++; } for(int i = 0; i < len - k; i++){ head = head.next; } return head.val; } }
若链表中的某个节点,既不是链表头节点,也不是链表尾节点,则称其为该链表的「中间节点」。
假定已知链表的某一个中间节点,请实现一种算法,将该节点从链表中删除。
例如,传入节点 c(位于单向链表 a->b->c->d->e->f 中),将其删除后,剩余链表为 a->b->d->e->f
思路
删除该个结点,其实可以删除后一个,把后一个的值赋给它就行了
代码
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public void deleteNode(ListNode node) { if(node.next == null){ node = null; }else if(node.next != null && node.next.next == null){ node.val = node.next.val; node.next = null; }else if(node.next != null && node.next.next != null){ node.val = node.next.val; node.next = node.next.next; } } }
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
你不需要 保留 每个分区中各节点的初始相对位置。
思路
新建两个结点用来指向大值和小值。最后把小值和大值连接起来。
代码
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode partition(ListNode head, int x) { ListNode small = new ListNode(0); ListNode smallHead = small; ListNode large = new ListNode(0); ListNode largeHead = large; while (head != null) { if (head.val < x) { small.next = head; small = small.next; } else { large.next = head; large = large.next; } head = head.next; } large.next = null; small.next = largeHead.next; return smallHead.next; } }
给定两个用链表表示的整数,每个节点包含一个数位。
这些数位是反向存放的,也就是个位排在链表首部。
编写函数对这两个整数求和,并用链表形式返回结果。
思路
之前考虑到求和在插入,发现容易溢出。我们现在使用flag判断需不需要进位。
代码
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode addTwoNumbers(ListNode l1, ListNode l2) { int sum = 0, flag = 0; int len1 = 0,len2 = 0; ListNode p = l1, pp = l1, q = l2, qq=l2; while(p != null){ len1++; p = p.next; } while(q != null){ len2++; q = q.next; } if(len1 <= len2){ while(qq != null){ sum = 0; if(pp != null){ sum = pp.val; pp = pp.next; } if(flag > 0){ sum += flag; flag = 0; } sum += qq.val; if(sum < 10){ qq.val = sum; }else{ qq.val = sum%10; flag = 1; } sum /= 10; if(qq.next == null){ break;} qq = qq.next; } if(sum > 0){ qq.next = new ListNode(1); } }else{ while(pp != null){ sum = 0; if(qq != null){ sum = qq.val; qq = qq.next; } if(flag > 0){ sum += flag; flag = 0; } sum += pp.val; if(sum < 10){ pp.val = sum; }else{ pp.val = sum%10; flag = 1; } sum /= 10; if(pp.next == null){ break;} pp = pp.next; } if(sum > 0){ pp.next = new ListNode(1); } } return len1<=len2?l2:l1; } }
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
思路
使用快慢指针(双指针)先使用快慢指针找到中间的位置,对中间后面的进行倒转。然后方便进行比较。
代码
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public boolean isPalindrome(ListNode head) { ListNode slow = head,fast = head,prev = null; while(fast != null && fast.next != null){ slow = slow.next; fast = fast.next.next; } while(slow != null){ ListNode temp = slow.next; slow.next = prev; prev = slow; slow = temp; } while(head!=null&&prev!=null){ if(head.val != prev.val){ return false; } head = head.next; prev = prev.next; } return true; } }
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
思路
假如有交点,那么假设未进入公共部分为a,b。然后公共部分为c,那么我们要建立一个等式,我们可以通过 a+c+b = b+c+a 来判断是否存在交点。同样目前的点正是所需要求的点。
代码
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { ListNode h1 = headA, h2 = headB; while(h1 != h2){ h1 = h1 == null?headB:h1.next; h2 = h2 == null?headA:h2.next; } return h1; } }
给定一个链表,如果它是有环链表,实现一个算法返回环路的开头节点。若环不存在,请返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
思路
双指针题,我们设置一个快指针fast 和一个慢指针slow,设定fast每次走两个,slow每次走一格
假设slow走x格以后他们相遇
此时fast走了2x格 设置 a为未进入循环的步长 b为循环步长 z为当前进入循环以后走的步长
有 x = a+z,2x = a+nb+z
n为循环的次数 我们暂时设置为1 则有
x = a + z ,2x = a+b+z
因为相遇 易知 x = b 又有 b = a+z
我们目前要求出a
我们利用头结点,同速度和slow跑易知a步以后他们会相遇 为什么?
因为 当走了a 步以后
此时 head a
slow a+a+z 即a + b
b为循环,则可求
此外对于 没有循环 我们只需要判断fast 和fast的next是否为空即可
代码
/** * Definition for singly-linked list. * class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public 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; } } while(head != slow){ head = head.next; slow = slow.next; } return head; } }
三合一。描述如何只用一个数组来实现三个栈。
你应该实现push(stackNum, value)、pop(stackNum)、isEmpty(stackNum)、peek(stackNum)方法。stackNum表示栈下标,value表示压入的值。
构造函数会传入一个stackSize参数,代表每个栈的大小。
思路
用个大小为3的index数组就可了
代码
class TripleInOne { int[] arr; int stackSize; int cur_num = 0; int[] indexs; public TripleInOne(int stackSize) { arr = new int[stackSize*3]; this.stackSize = stackSize; indexs = new int[]{0, 1, 2}; } public void push(int stackNum, int value) { if(indexs[stackNum] >= stackSize * 3){ return; } arr[indexs[stackNum]] = value; indexs[stackNum] += 3; } public int pop(int stackNum) { if(isEmpty(stackNum)){ return -1; } indexs[stackNum] -= 3; return arr[indexs[stackNum]]; } public int peek(int stackNum) { if(isEmpty(stackNum)){ return -1; } return arr[indexs[stackNum] - 3]; } public boolean isEmpty(int stackNum) { return indexs[stackNum] < 3; } } /** * Your TripleInOne object will be instantiated and called as such: * TripleInOne obj = new TripleInOne(stackSize); * obj.push(stackNum,value); * int param_2 = obj.pop(stackNum); * int param_3 = obj.peek(stackNum); * boolean param_4 = obj.isEmpty(stackNum); */
请设计一个栈,除了常规栈支持的pop与push函数以外,还支持min函数,该函数返回栈元素中的最小值。执行push、pop和min操作的时间复杂度必须为O(1)
思路
多使用一个栈用来储存每次栈中最小值。
代码
class MinStack { int min = Integer.MAX_VALUE; Stack<Integer> valStack = new Stack<>(); Stack<Integer> minStack = new Stack<>(); /** initialize your data structure here. */ public MinStack() { } public void push(int x) { min = Math.min(min, x); valStack.push(x); minStack.push(min); } public void pop() { valStack.pop(); minStack.pop(); if (minStack.isEmpty()) { min = Integer.MAX_VALUE; } else { min = minStack.peek(); } } public int top() { return valStack.peek(); } public int getMin() { return min; } } /** * Your MinStack object will be instantiated and called as such: * MinStack obj = new MinStack(); * obj.push(x); * obj.pop(); * int param_3 = obj.top(); * int param_4 = obj.getMin(); */
堆盘子。设想有一堆盘子,堆太高可能会倒下来。因此,在现实生活中,盘子堆到一定高度时,我们就会另外堆一堆盘子。请实现数据结构SetOfStacks,模拟这种行为。SetOfStacks应该由多个栈组成,并且在前一个栈填满时新建一个栈。此外,SetOfStacks.push()和SetOfStacks.pop()应该与普通栈的操作方法相同(也就是说,pop()返回的值,应该跟只有一个栈时的情况一样)。 进阶:实现一个popAt(int index)方法,根据指定的子栈,执行pop操作。
当某个栈为空时,应当删除该栈。当栈中没有元素或不存在该栈时,pop,popAt 应返回 -1.
思路
创建一个List<Stack<>>,设置大小,用来创建多个栈。
代码
class StackOfPlates { List<Stack<Integer>> stacks = new ArrayList<>(); int cap; public StackOfPlates(int cap) { this.cap = cap; } public void push(int val) { if(cap == 0) return; if(stacks.isEmpty()) stacks.add(new Stack<>()); if(stacks.get(stacks.size() - 1).size() == cap) stacks.add(new Stack<>()); stacks.get(stacks.size() - 1).push(val); } public int pop() { return popAt(stacks.size() - 1); } public int popAt(int index) { if (cap == 0 || index < 0 || index >= stacks.size()) return -1; int pop = stacks.get(index).pop(); if (stacks.get(index).empty()) stacks.remove(index); return pop; } } /** * Your StackOfPlates object will be instantiated and called as such: * StackOfPlates obj = new StackOfPlates(cap); * obj.push(val); * int param_2 = obj.pop(); * int param_3 = obj.popAt(index); */
实现一个MyQueue类,该类用两个栈来实现一个队列。
思路
设置两个栈一个用来存数据一个用来放数据。但放数据的栈为空,那么我们吧存数据的栈的数据放到放数据里面。
代码
class MyQueue { Stack<Integer> stackWrite; // 存数据 Stack<Integer> stackRead; // 读数据 /** Initialize your data structure here. */ public MyQueue() { stackWrite = new Stack<>(); stackRead = new Stack<>(); } /** Push element x to the back of queue. */ public void push(int x) { stackWrite.push(x); } /** Removes the element from in front of queue and returns that element. */ public int pop() { peek(); return stackRead.pop(); } /** Get the front element. */ public int peek() { if (!stackRead.isEmpty()) { return stackRead.peek(); } while (!stackWrite.isEmpty()) { stackRead.push(stackWrite.pop()); } return stackRead.peek(); } /** Returns whether the queue is empty. */ public boolean empty() { return stackRead.isEmpty() && stackWrite.isEmpty(); } } /** * Your MyQueue object will be instantiated and called as such: * MyQueue obj = new MyQueue(); * obj.push(x); * int param_2 = obj.pop(); * int param_3 = obj.peek(); * boolean param_4 = obj.empty(); */
栈排序。 编写程序,对栈进行排序使最小元素位于栈顶。最多只能使用一个其他的临时栈存放数据,但不得将元素复制到别的数据结构(如数组)中。该栈支持如下操作:push、pop、peek 和 isEmpty。当栈为空时,peek 返回 -1。
思路
使用一个临时栈用来维持最小的数
代码
class SortedStack { Stack<Integer> stackOrd =new Stack<Integer>(); Stack<Integer> stackSto =new Stack<Integer>(); public SortedStack() { } public void push(int val) { while(!stackOrd.isEmpty() && stackOrd.peek()<val){ stackSto.push(stackOrd.pop()); } stackOrd.push(val); while(!stackSto.isEmpty()){ stackOrd.push(stackSto.pop()); } } public void pop() { if(!stackOrd.isEmpty()){ stackOrd.pop(); } } public int peek() { if(stackOrd.isEmpty()){ return -1; }else{ return stackOrd.peek(); } } public boolean isEmpty() { return stackOrd.isEmpty(); } } /** * Your SortedStack object will be instantiated and called as such: * SortedStack obj = new SortedStack(); * obj.push(val); * obj.pop(); * int param_3 = obj.peek(); * boolean param_4 = obj.isEmpty(); */
动物收容所。有家动物收容所只收容狗与猫,且严格遵守“先进先出”的原则。在收养该收容所的动物时,收养人只能收养所有动物中“最老”(由其进入收容所的时间长短而定)的动物,或者可以挑选猫或狗(同时必须收养此类动物中“最老”的)。换言之,收养人不能自由挑选想收养的对象。请创建适用于这个系统的数据结构,实现各种操作方法,比如enqueue、dequeueAny、dequeueDog和dequeueCat。允许使用Java内置的LinkedList数据结构。
enqueue方法有一个animal参数,animal[0]代表动物编号,animal[1]代表动物种类,其中 0 代表猫,1 代表狗。
dequeue*方法返回一个列表[动物编号, 动物种类],若没有可以收养的动物,则返回[-1,-1]。
思路
使用两个队列分别用来存储猫和狗,另外设置一个时间t,用来判断哪个存的久
代码
class AnimalShelf { Queue<int[]> dogs = new LinkedList<>(); Queue<int[]> cats = new LinkedList<>(); int t = 0; public AnimalShelf() { } public void enqueue(int[] animal) { if (animal[1] == 0) { cats.offer(new int[]{animal[0], t++}); } else { dogs.offer(new int[]{animal[0], t++}); } } public int[] dequeueAny() { if (cats.isEmpty() && dogs.isEmpty()) { return new int[]{-1, -1}; } if (dogs.isEmpty()) { return dequeueCat(); } if (cats.isEmpty()) { return dequeueDog(); } return cats.peek()[1] < dogs.peek()[1] ? dequeueCat() : dequeueDog(); } public int[] dequeueDog() { if (dogs.isEmpty()) { return new int[]{-1, -1}; } return new int[]{dogs.poll()[0], 1}; } public int[] dequeueCat() { if (cats.isEmpty()) { return new int[]{-1, -1}; } return new int[]{cats.poll()[0], 0}; } } /** * Your AnimalShelf object will be instantiated and called as such: * AnimalShelf obj = new AnimalShelf(); * obj.enqueue(animal); * int[] param_2 = obj.dequeueAny(); * int[] param_3 = obj.dequeueDog(); * int[] param_4 = obj.dequeueCat(); */
节点间通路。给定有向图,设计一个算法,找出两个节点之间是否存在一条路径。
思路
使用hashmap,存储当前结点能到的结点。
代码
class Solution { public boolean findWhetherExistsPath(int n, int[][] graph, int start, int target) { Map<Integer, Set<Integer>> cs = new HashMap<>(); for (int i = 0; i < graph.length; i++) { cs.computeIfAbsent(graph[i][0], k -> new HashSet<>()).add(graph[i][1]); } if(cs.get(start) == null){ return false; } int len = 0; Set<Integer> temp = new HashSet<>(); while(len != cs.get(start).size()){ len = cs.get(start).size(); for (int s:cs.get(start)) { if(s == target){ return true; } if(cs.get(s) != null){ for(int c:cs.get(s)){ temp.add(c); if(c == target){ return true; } } } } for(int temps:temp){ cs.get(start).add(temps); } temp.clear(); } return false; } }
给定一个有序整数数组,元素各不相同且按升序排列,编写一个算法,创建一棵高度最小的二叉搜索树。
思路
二分查找
代码
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public TreeNode sortedArrayToBST(int[] nums) { return helper(nums,0,nums.length); } private TreeNode helper(int[] nums,int left,int right){ if(left==right){ return null; } int mid=left+(right-left)/2; TreeNode node = new TreeNode(nums[mid]); node.left = helper(nums,left,mid); node.right = helper(nums,mid+1,right); return node; } }
给定一棵二叉树,设计一个算法,创建含有某一深度上所有节点的链表(比如,若一棵树的深度为 D,则会创建出 D 个链表)。返回一个包含所有深度的链表的数组。
思路
创建一个队列,每次把该层的结点存进去,如果该层为空则结束循环
代码
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode[] listOfDepth(TreeNode tree) { if(tree == null){ return new ListNode[0]; } Queue<TreeNode> queue = new LinkedList<>(); queue.add(tree); List<ListNode> list = new ArrayList<>(); ListNode dummyHead = new ListNode(-1); while(!queue.isEmpty()){ ListNode cur = dummyHead; int size = queue.size(); for(int i = 0; i<size; i++){ tree = queue.poll(); cur.next = new ListNode(tree.val); cur = cur.next; if (tree.left != null) { queue.offer(tree.left); } if (tree.right != null) { queue.offer(tree.right); } } list.add(dummyHead.next); } ListNode[] res = new ListNode[list.size()]; for (int i = 0; i < list.size(); i++) { res[i] = list.get(i); } return res; } }
实现一个函数,检查二叉树是否平衡。在这个问题中,平衡树的定义如下:任意一个节点,其两棵子树的高度差不超过 1。
思路
dfs + 一个判断就可以了
代码
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { boolean flag = true; public boolean isBalanced(TreeNode root) { dfs(root); return flag; } public int dfs(TreeNode root){ if(root == null) return 0; int left = dfs(root.left); int right = dfs(root.right); if(Math.abs(left - right) > 1){ flag = false; } return Math.max(left, right) + 1; } }
实现一个函数,检查一棵二叉树是否为二叉搜索树。
思路
创建一个队列,用来存储结点,每次进行比较。
代码
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public boolean isValidBST(TreeNode root) { Integer prev = null; Deque<TreeNode> deque = new LinkedList<>(); TreeNode node = root; while(node != null|| !deque.isEmpty()) { while (node != null) { deque.push(node); node = node.left; } node = deque.pop(); if (prev != null && prev >= node.val) { return false; } prev = node.val; node = node.right; } return true; } }
设计一个算法,找出二叉搜索树中指定节点的“下一个”节点(也即中序后继)。
如果指定节点没有对应的“下一个”节点,则返回null。
思路
因为是二叉搜索树,我们进行比较即可
代码
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public TreeNode inorderSuccessor(TreeNode root, TreeNode p) { if (root == null) { return null; } if (p.val >= root.val) { return inorderSuccessor(root.right, p); } TreeNode node = inorderSuccessor(root.left, p); return node == null ? root : node; } }
设计并实现一个算法,找出二叉树中某两个节点的第一个共同祖先。不得将其他的节点存储在另外的数据结构中。注意:这不一定是二叉搜索树。
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
思路
深度遍历 dfs
代码
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */ class Solution { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { if (root == null) return null; if (p == root || q == root) return root; TreeNode left = lowestCommonAncestor(root.left, p, q); TreeNode right = lowestCommonAncestor(root.right, p, q); if (left != null && right != null) return root; return left == null ? right : left; } }
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
思路
动态规划,本来打算用一个mn的数组,事实上只用n的数组就可以了,因为我们只需要存储一列的数字,fn只能从上面或者左边来。
代码
class Solution { public int maxValue(int[][] grid) { int m = grid.length; int n = grid[0].length; int[] fn = new int[n]; fn[0] = grid[0][0]; for(int i = 1; i < n; i++){ fn[i] = fn[i-1] + grid[0][i]; } for(int i = 1;i < m; i++){ fn[0] = fn[0] + grid[i][0]; for(int j = 1; j < n; j++){ fn[j] = Math.max(fn[j]+grid[i][j],fn[j-1]+grid[i][j]); } } return fn[n-1]; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。