目录
160:相交链表
同剑指offer52:两个链表的第一个公共节点
/**
* 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) {
if (headA == null || headB == null) return null;
ListNode pA = headA, pB = headB;
while(pA != pB){
pA = pA == null ? headB : pA.next;
pB = pB == null ? headA : pB.next;
}
return pA;
}
}
169:多数元素
思路:返回数组中出现次数大于n/2
的元素。构建map,key存放数组中元素,value存放该元素出现的次数,当大于n/2的时候输出即可
class Solution {
public int majorityElement(int[] nums) {
HashMap<Integer, Integer> map = new HashMap<>();
for(int num : nums){
map.put(num, map.getOrDefault(num, 0) + 1);
if(map.get(num) > nums.length / 2) return num;
}
return 0;
}
}
461:汉明距离
思路:该题关键在于怎么数异或后结果中1的个数。n & (n - 1)的作用就是每次去掉二进制中最右侧的一个1,去掉一个count计数+1,当n变为0的时候说明1数完了
class Solution {
public int hammingDistance(int x, int y) {
//n & n-1 的值是去掉二进制n最右边1的值
int res = x ^ y; //异或后1的个数即为不同的个数
int count = 0;
while(res != 0){
res &= (res - 1);
count++;
}
return count;
}
}
448:找到所有数组中消失的数字
思路:数组nums中所有元素都在1~n范围内,可能会有重复,但所有存在的元素-1之后一定都有索引,所以将存在的索引对应的元素标记为1,而元素为0对应的索引+1就是不存在的数
(索引是从0开始的)
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
int[] temp = new int[nums.length];
List<Integer> res = new ArrayList<Integer>();
for(int i = 0; i < nums.length; i++){
temp[nums[i] - 1] = 1;
//索引是0开始的,将不缺的数字都-1就和索引对上了,缺了谁谁作为索引对应的元素就是0
}
for(int i = 0; i < temp.length; i++){
if(temp[i] == 0) res.add(i + 1);
}
return res;
}
}
338:比特位计数(有点难理解)
思路:数组输出n以内每个数的二进制形式。
class Solution {
public int[] countBits(int n) {
//这也太绕了吧
//n & n-1 的值是去掉二进制n最右边1的值
//去掉最右的1,然后用去掉后的二进制对应的1的个数再加上去掉的1个1
//还是动态规划
//00 01 10 11 100 101 110 111 1000 1001 1010 1011 1100 1101 1110 1111
int[] res = new int[n + 1];
for(int i = 1; i < n + 1; i++){
res[i] = res[i & (i - 1)] + 1;
}
return res;
}
}
283:移动零
思路:先遍历数组得到0的个数,然后再遍历一遍数组,将不为0的元素从索引0开始依次放入数组中,最后将倒数count个元素填充为0即可
class Solution {
public void moveZeroes(int[] nums) {
int count= 0;
for(int i = 0; i < nums.length; i++){
if(nums[i] == 0) count++;
}
int j = 0;
for(int i = 0; i < nums.length; i++){
if(nums[i] != 0){
nums[j] = nums[i];
j++;
}
}
for(int i = nums.length - count; i < nums.length; i++){
nums[i] = 0;
}
}
}
62:不同路径
思路:动态规划。第一行和第一列中的空格只有一种来源,除此之外的空格都等于它的上侧和左侧路个数相加之和。
class Solution {
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
//第一行和第一列的只有一种走法
for(int i = 0; i < m; i++) dp[i][0] = 1;
for(int i = 0; i < n; i++) dp[0][i] = 1;
for(int i = 1; i < m; i++){
for(int j = 1; j < n; j++){
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
}
279:完全平方数(这题多理解)
思路:动态规划。最坏的情况就是由n个1相加得到,然后在这个基础上判断是否有比n个更少的完全平方数相加可以得到n。j从1开始,j*j在小于n的范围内遍历。
class Solution {
public int numSquares(int n) {
int[] dp = new int[n + 1];
for(int i = 1; i <= n; i++){
dp[i] = i; //最多的情况就是全是1相加得到n
for(int j = 1; j * j <= i; j++){
//dp[i]里存放的是从1~n每个数最少的完全平方数数量
//如果dp[i]可以由完全平方数组成,那就i-j*j,继续判断差是否可以由完全平方数组成
dp[i] = Math.min(dp[i], dp[i - j * j] + 1);
}
}
return dp[n];
}
}
64:最小路径和
思路:动态规划。这次空格里有数字,除了第一行和第一列的空格其他的是从上或左来的,所以挑选上或左最小的叠加即可。
class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length, n = grid[0].length;
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(i == 0 && j == 0) continue;
else 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.min(grid[i][j - 1], grid[i - 1][j]);
}
}
return grid[m - 1][n - 1];
}
}
75:颜色分类
思路:一共三种颜色(数字0/1/2),遍历得到三个数字的个数,再遍历数组一次放入三个数字。
(俗称笨办法)
class Solution {
public void sortColors(int[] nums) {
int count0 = 0, count1 = 0, count2 = 0;
int flag = 0;
for(int num : nums){
if(num == 0) count0++;
else if(num == 1) count1++;
else count2++;
}
for(int i = 0; i < nums.length; i++){
if(i < count0) nums[i] = 0;
else if(i >= count0 && i < count0 + count1) nums[i] = 1;
else if(i >= count0 + count1) nums[i] = 2;
}
}
}
322:零钱兑换(现在看不懂,下次再看)
思路:动态规划类同279:完全平方数。
class Solution {
public int coinChange(int[] coins, int amount) {
//类同完全平方数
int[] dp = new int[amount + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for(int coin : coins){
for(int j = coin; j <= amount; j++){
//dp记录多少个coin可以组成j
//遍历coins,得到每个coin组成j所用的最少的数量
if(dp[j - coin] != Integer.MAX_VALUE)
dp[j] = Math.min(dp[j], dp[j - coin] + 1);
}
}
//不能组合的话返回-1
return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
}
}
287:寻找重复数(这也难理解了)
思路:二分查找。数字都在1~n范围内,(是不是用索引判断的?)当left和right指向同个数就是重复的数
class Solution {
public int findDuplicate(int[] nums) {
//二分
int left = 1, right = nums.length - 1;
int res = -1;
while(left < right){
int mid = (left + right) / 2;
int count = 0;
for(int i = 0; i < nums.length; i++){
if(nums[i] <= mid) count++;
}
if(count > mid) right = mid;
else left = mid + 1;
}
return left;
}
}
96:不同的二叉搜索树
思路:动态规划。搜索树左子节点<根节点<右子节点,树的种类=左子树种类*右子树的种类
class Solution {
public int numTrees(int n) {
int[] dp = new int[n + 1];
dp[0] = dp[1] = 1;
for(int i = 2; i <= n; i++){
//最大节点值从2开始
for(int j = 1; j <= i; j++){
int left = dp[j - 1]; //比i小的都是左子树,排列方法有dp[j - 1]种
int right = dp[i - j]; //比i大的是右子树
//相乘后为总的可能种类
dp[i] += left * right;
}
}
return dp[n];
}
}
739:每日温度
思路:i从后往前遍历,每个j从i的下一个开始遍历,小于和等于的填充为0,寻找大于t [ i ]的温度,然后用索引相减就是天数。遍历结束还没找到的话那就是0.
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int len = temperatures.length;
int[] res = new int[len];
for(int i = len - 2; i >= 0; i--){
for(int j = i + 1; j < len; j += res[j]){
if(temperatures[j] > temperatures[i]){
//遇到比i大的温度直接下标相减就是天数,跳出
res[i] = j - i;
break;
} else if(res[j] == 0){//没有更大的置为0
res[i] = 0;
break;
}
}
}
return res;
}
}
34:在排序数组中查找元素的第一个和最后一个位置
思路:二分查找。排序数组中查找就比较容易了...
class Solution {
public int[] searchRange(int[] nums, int target) {
if(nums.length == 0) return new int[]{-1, -1};
int left = 0, right = nums.length - 1;
while(left <= right){
int mid = (left + right) / 2;
if(nums[mid] < target) left = mid + 1;
else if(nums[mid] > target) right = mid - 1;
else{
if(nums[left] != target) left++;
else if(nums[right] != target) right--;
else break;
}
}
if(left <= right) return new int[]{left, right};
else return new int[]{-1, -1};
}
}
39:组合总和(看这题理解万恶的回溯)
思路:我恨回溯!每次回溯结束后要移除list里最后一个元素是因为回溯的是在这个元素基础上对从它开始之后的元素进行循环,所以这一轮循环结束后移除最后一个元素,再回到上一层循环添加新的末尾元素继续循环。(这段话可能上个厕所我就看不懂了)
class Solution {
List<List<Integer>> res;
List<Integer> list;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
res = new ArrayList<>();
list = new ArrayList<>();
fun(candidates, target, 0);
return res;
}
public void fun(int[] candidates, int target, int index){
if(target == 0){
//已经不需要再凑数的时候返回即可
res.add(new ArrayList<>(list));
return;
}
for(int i = index; i < candidates.length; i++){
if(candidates[i] <= target){
//当前元素小于目标值即可以作为凑数的一员
list.add(candidates[i]);
//因为可以重复利用一个元素,所以内部继续调用
fun(candidates, target - candidates[i], i);
//递归结束后将刚才放进去的值删除,回溯 ???
list.remove(list.size() - 1);
}
}
}
}
300:最长递增子序列
思路:动态规划。对于nums中每个元素,遍历到的时候判断从0到当前元素的位置查找比它小的元素个数,但要注意不只是小,还要是递增的。
class Solution {
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
Arrays.fill(dp, 1);
int count = 0;
if(nums.length == 0) return 0;
for(int i = 0; i < nums.length; i++){
for(int j = 0; j < i; j++){
if(nums[j] < nums[i])
//dp[i]记录的是比nums[i]小的有多少个
//因为它前面可能有多个元素比它小,但这些元素不一定递增,所以需要记录个数最大的
dp[i] = Math.max(dp[i], dp[j] + 1);
}
//每次挑出最大的
count = Math.max(count, dp[i]);
}
return count;
}
}
46:全排列
思路:领悟需要时间,我还是恨回溯,怎么就这么就不能豁然开朗一举拿下😭
class Solution {
//多看看这道题,调试的时候看过程怎么进行的,理解了这个回溯积就理解了
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
int[] cur = new int[nums.length];
fun(res, nums, new ArrayList<Integer>(), cur);
return res;
}
//temp存放结果队列中的每个内嵌队列
public void fun(List<List<Integer>> res, int[] nums, ArrayList<Integer> temp, int[] cur){
if(temp.size() == nums.length){
//当临时的队列和数组长度相同的时候放入结果队列中
res.add(new ArrayList(temp));
return;
}
for(int i = 0; i < nums.length; i++){
//cur[i] = 1表示当前nums[i]已经访问过了,继续访问i+1
if(cur[i] == 1) continue;
//访问到的时候要将cur标记为1
cur[i] = 1;
temp.add(nums[i]);
//每个nums[i]都要回溯
fun(res, nums, temp, cur);
//回溯结束后要删掉当前进行回溯的nums[i]并将cur置为0
cur[i] = 0;
//并删掉临时添加在temp中的nums[i]
temp.remove(temp.size() - 1);
}
}
}