算法练习---力扣(更新中ING)

算法练习

文章目录

1.(力扣练习)最大连续数字組(动态规划)

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

方法一:动态规划

思路:正數加正數總是大於他們各自本身。

public int maxSubArray(int[] arr) {
		int res = arr[0];
		int sum = 0;

		for (int i = 0; i < arr.length; i++) {
			if (sum > 0)
				sum = sum + arr[i];
			else
				sum = arr[i];
			res = res > sum ? res : sum;
		}
		return res;
	}

方法二:分治法

public int func2(int[] arr, int left, int right) {
		if (left == right)
			return arr[left];
		int mid = (left + right) / 2;

		return Math.max(func2(arr, left, mid), Math.max(func2(arr, mid + 1, right), allSubPart(arr, left, mid, right)));
	}

	private int allSubPart(int[] arr, int left, int mid, int right) {
		int leftSum = 0;
		int sum = 0;
		for (int i = mid; i >= left; i--) {
			sum += arr[i];
			if (sum > leftSum) {
				leftSum = sum;
			}
		}
		sum = 0;
		int rightSum = Integer.MIN_VALUE;
		for (int i = mid + 1; i <= right; i++) {
			sum += arr[i];
			if (sum > rightSum) {
				rightSum = sum;
			}
		}
		return leftSum + rightSum;
	}

2.完全背包问题(动态规划)

有n种重量和价值分别为w[i]、v[i](1≤i≤n)的物品,从这些物品中挑选总重量不超过W的物品,求出挑选物品价值总和最大的挑选方案,这里每种物品可以挑选任意多件

n=4

W = 7

w = {0,3,4,2}

v = {0,4,5,3}

01234567
000000000
1000dp[1,3]=max(dp[0,3],dp[1,3-w[1]]+v[1])=444dp[1,6]=max(dp[0,6],dp[1,6-w[1]*2]+v[1]*2)=88
20004dp[2,4]=max(dp[1,4],dp[2,4-w[2]]+v[2])=558dp[2,7]=max(dp[1,7],dp[2,7-w[2]]+v[2])=9
30034dp[3,4]=max(dp[2,4],dp[3,4-w[3]*2]+v[3]*2)=6dp[3,5]=max(dp[2,5],dp[3,5-w[3]]+v[3])=7910

動態轉移方程式:


dp[i,j] = max(dp[i,j], max(dp[i -1,j], dp[i,j - w[i] * k] + v[i] * k))

= max(dp[i,j], max(dp[i - 1,j], dp[i,j - 1]))

public int solve(int n, int[] weight, int[] value, int W) {
		int[][] dp = new int[n][W + 1];
		for (int i = 1; i < n; i++) {
			for (int j = 1; j <= W; j++) {
				for (int k = 0; k * weight[i] <= j; k++) {
					if (j - weight[i] >= 0)
						dp[i][j] = Math.max(dp[i][j], Math.max(dp[i - 1][j], dp[i][j - weight[i] * k] + value[i] * k));
					else
						dp[i][j] = Math.max(dp[i][j], Math.max(dp[i - 1][j], dp[i][j - 1]));
				}
			}
		}
		return dp[n - 1][W];
	}

3.扔鸡蛋问题(动态规划)

有2个鸡蛋,从100层楼上往下扔,以此来测试鸡蛋的硬度。比如鸡蛋在第9层没有摔碎,在第10层摔碎了,那么鸡蛋不会摔碎的临界点就是9层。

问:如何用最少的尝试次数,测试出鸡蛋不会摔碎的临界点?

思路:

设x为最大测试次数,则第一次就从x层开始

  1. 硬度为x-1,则剩下的鸡蛋需要测试x-1,加上本次1,则为x次,满足假设

  2. 若硬度大于等于x,则下次从x-1+x层开始

    1. 若硬度为x+x-2则,总共需要x次,满足条件

    2. 若硬度大于等于x+x-1,则下次从x+x-1+x-2开始

最坏情况,硬度为100,则总次数为:

x+x-1+x-2+ … + 1 = 100

x^2 + x - 200 = 0

x = 14

动态转移方程式:


dp[i,j]=min(max(dp[i-k,j],dp[k-1,j-1])+1)

public int solve(int n, int x) {
		int[][] dp = new int[n + 1][x + 1];
		for (int i = 0; i < n + 1; i++) {
			dp[i][1] = i;
		}
		for (int i = 1; i < n + 1; i++) {
			for (int j = 2; j < x + 1; j++) {
				int min = Integer.MAX_VALUE;
				for (int k = 1; k <= i; k++) {
					int x1 = dp[i - k][j];
					int x2 = dp[k - 1][j - 1];
					int max = Math.max(x1, x2) + 1;
					min = Math.min(min, max);
				}
				dp[i][j] = min;
			}
		}
		return dp[n][x];
	}

4.(力扣练习)比特位计数(技巧)

给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。

输入: 2
输出: [0,1,1]
输入: 5
输出: [0,1,1,2,1,2]
    public int[] countBits(int num) {
        int ans[]=new int[num+1];
        for(int i=0;i<=num;i++){
            ans[i]=Function(i);
        }
        return ans;
    }
    int Function(int n) {
        /*
        * 思路:
        * 先每给进行计算,找到1,
        * 然后每两个,每四个,每八个,每十六个
        * 0101 0101 0101 0101 0101 0101 0101 0101
        * 0011 0011 0011 0011 0011 0011 0011 0011
        * 0000 1111 0000 1111 0000 1111 0000 1111
        * 0000 0000 1111 1111 0000 0000 1111 1111
        * 0000 0000 0000 0000 1111 1111 1111 1111
        *
        * >> 右位移 << 左位移 <<< 无符号位移
        * 为什么要右位移:
        * 因为0x55555555比较的是单数为的,右位移后变成双数位的也可以计算1的数量
        * 此处可以改为:
        *
        * 为什么不左位移:
        * 因为int行的数字,最大为32为整型,左位移可能超界
        * */
        n = (n & 0x55555555) + ((n >> 1) & 0x55555555);
        n = (n & 0x33333333) + ((n >> 2) & 0x33333333);
        n = (n & 0x0f0f0f0f) + ((n >> 4) & 0x0f0f0f0f);
        n = (n & 0x00ff00ff) + ((n >> 8) & 0x00ff00ff);
        n = (n & 0x0000ffff) + ((n >> 16) & 0x0000ffff);
        return n;
    }

5.(力扣练习)除自身以外数组的乘积(技巧)

给定长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。

示例:

输入: [1,2,3,4]
输出: [24,12,8,6]
说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。

进阶:
你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)

思路:

除自身外其他元素:分为左边部分,右边部分

left:从左到右,依次累乘,每次赋值,然后累乘

right:从右到左,依次累乘,每次赋值,然后累乘

public int[] productExceptSelf(int[] nums) {
        int len = nums.length;
        int[] res = new int[len];

        int right = 1;
        int left = 1;

        for (int i = 0; i < len; i++) {
            res[i] = left;//初始化 对数据进行赋值
            left *= nums[i];
        }
        for (int i = len - 1; i >= 0; i--) {
            res[i] *= right;//对数据进行二次赋值
            right *= nums[i];
        }

        return res;
    }

6.(力扣练习)寻找重复数(三方法)

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

示例 1:

输入: [1,3,4,2,2]
输出: 2
示例 2:

输入: [3,1,3,4,2]
输出: 3
说明:

不能更改原数组(假设数组是只读的)。
只能使用额外的 O(1) 的空间。
时间复杂度小于 O(n^2) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。

方法一:快慢指针

思路:

使用快慢指针找到循环体,然后依次遍历循环体和本体,找到重复值:

public int findDuplicate(int[] nums) {
        //循环检测--快慢指针
        int fast = nums[0];
        int slow = nums[0];
        do {//找到循环
            slow = nums[slow];
            fast = nums[nums[fast]];
        } while (slow != fast);
        int ptr1 = nums[0];
        int ptr2 = fast;
        while (ptr1 != ptr2) {//循环和数组间的遍历,找到重复元素
            ptr1 = nums[ptr1];
            ptr2 = nums[ptr2];
        }
        return ptr1;
    }

方法二:排序依次遍历

public int findDuplicate(int[] nums) {
        Arrays.sort(nums);
        int ptr = 1;
        while (nums[ptr] != nums[ptr - 1])
            ptr++;
        return nums[ptr];
    }

方法三:使用set特性保存数组

public int findDuplicate(int[] nums) {
        Set<Integer> set = new HashSet<>();
        for (int i = 0, len = nums.length; i < len; i++) {
            if (set.contains(nums[i]))
                return nums[i];
            set.add(nums[i]);
        }
        return -1;
    }

7.(力扣练习)颜色分类(两方法)

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

注意:
不能使用代码库中的排序函数来解决这道题。

示例:

输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]
进阶:

一个直观的解决方案是使用计数排序的两趟扫描算法。
首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。
你能想出一个仅使用常数空间的一趟扫描算法吗?

方法一:快速排序(O(nlogn))

public void sortColors(int[] nums) {
        quickSort(nums, 0, nums.length - 1);
    }

    private void quickSort(int[] nums, int right, int left) {
        if (right >= left)
            return;
        else {
            int r = right;
            int l = left;
            int temp = nums[r];
            while (r != l) {
                while (r != l) {
                    if (nums[l] < temp) {
                        nums[r] = nums[l];
                        break;
                    } else
                        l--;
                }
                while (r != l) {
                    if (nums[r] > temp) {
                        nums[l] = nums[r];
                        break;
                    } else
                        r++;
                }
            }
            int index = r;
            nums[index] = temp;
            quickSort(nums, right, index - 1);
            quickSort(nums, index + 1, left);
        }
    }

方法二:荷兰国旗问题

思路:

用三个指针:

​ left 当ptr值为0时与left替换,left自加,ptr自加(保证left之前一定是0)

​ right 当ptr为2时与right替换,right自减(保证right之后的一定是2,ptr不变,因为可能替换后是1,0,所以交给下一次循环判断,是否是1,或者0)

public void sortColors(int[] nums) {
        /*
         * 思路:
         * left 当ptr值为0时与left替换,left自加,ptr自加
         * right 当ptr为2时与right替换,right自减,ptr自加
         * */
        int left = 0;
        int right = nums.length - 1;
        int ptr = 0;
        int temp;
        while (ptr <= right) {
            if (nums[ptr] == 0) {
                temp = nums[ptr];
                nums[ptr] = nums[left];
                nums[left] = temp;
                left++;
                ptr++;
            } else if (nums[ptr] == 2) {
                temp = nums[ptr];
                nums[ptr] = nums[right];
                nums[right] = temp;
                right--;
            } else
                ptr++;
        }
    }

8.(力扣练习)房间与钥匙(DFS)

有 N 个房间,开始时你位于 0 号房间。每个房间有不同的号码:0,1,2,…,N-1,并且房间里可能有一些钥匙能使你进入下一个房间。

在形式上,对于每个房间 i 都有一个钥匙列表 rooms[i],每个钥匙 rooms[i][j] 由 [0,1,…,N-1] 中的一个整数表示,其中 N = rooms.length。

钥匙 rooms[i][j] = v 可以打开编号为 v 的房间。最初,除 0 号房间外的其余所有房间都被锁住。

你可以自由地在房间之间来回走动。如果能进入每个房间返回 true,否则返回 false。

方法一:DFS

思路:

通过第一次遍历进入房间,然后通过该房间钥匙依次进入下一个房间,若房间中钥匙遍历完则返回。

public boolean canVisitAllRooms(List<List<Integer>> rooms) {
        int size = rooms.size();
        boolean[] openRooms = new boolean[size];//设置房间数量
        visitRoom(rooms, 0, openRooms);//进入第一个房间
        for (boolean b : openRooms) {//判断是否每个房间都以开放
            if(!b)
                return false;
        }
        return true;
    }

    private void visitRoom(List<List<Integer>> rooms, int room, boolean[] openRooms) {
        //如果进入过该房间则退出,否则进入该房间,并遍历所有的钥匙
        if (openRooms[room])
            return;
        else {
            openRooms[room] = true;
            for (int i = 0, size = rooms.get(room).size(); i < size; i++) {
                int index = rooms.get(room).get(i);//获取下一个钥匙
                visitRoom(rooms, index, openRooms);//进入对应的房间
            }
        }
    }

9.(力扣练习)使用最小花费爬楼梯(动态规划)

数组的每个索引做为一个阶梯,第 i个阶梯对应着一个非负数的体力花费值 costi

每当你爬上一个阶梯你都要花费对应的体力花费值,然后你可以选择继续爬一个阶梯或者爬两个阶梯。

您需要找到达到楼层顶部的最低花费。在开始时,你可以选择从索引为 0 或 1 的元素作为初始阶梯。

方法一:动态规划

思路:

dp方程式:

​ dp[i] = cost[i] + min(dp[i-1],dp[i-2])(i>=2)

public int minCostClimbingStairs(int[] cost) {
    //原始dp动态方程式
        int len = cost.length;
        int[] dp = new int[len + 1];

        for (int i = 0; i < len + 1; i++) {
            if (i == 0 || i == 1)
                dp[i] = 0;
            else {
                dp[i] = Math.min(dp[i - 1] + cost[i-1], dp[i - 2] + cost[i-2]);
            }
        }

        return dp[len];
    }
public int minCostClimbingStairs(int[] cost) {
    //第一次优化dp动态方程式,在原来数组基础上保存数据
        int len = cost.length;

        for (int i = 2; i < len; i++) {
            cost[i] = cost[i] + Math.min(cost[i - 1], cost[i - 2]);
        }

        return Math.min(cost[len - 1], cost[len - 2]);
    }
public int minCostClimbingStairs(int[] cost) {
        //第二次优化,使用两个变量保存每次更新的两个值
        int len = cost.length;
        int f1 = 0, f2 = 0;

        for (int i = len - 1; i >= 0; --i) {//倒序查询
            int f0 = cost[i] + Math.min(f1, f2);
            f2 = f1;
            f1 = f0;
        }
        return Math.min(f1, f2);
    }

10.(力扣练习)求众数 II(三方法)

给定一个大小为 n 的数组,找出其中所有出现超过 ⌊ n/3 ⌋ 次的元素。

说明: 要求算法的时间复杂度为 O(n),空间复杂度为 O(1)。

方法一:暴力法

思路:

使用map计数

public List<Integer> majorityElement(int[] nums) {
        //原始方法,使用map计数(效率低,内存消耗高,书写简单)
        Map<Integer, Integer> map = new HashMap<>();
        List<Integer> ans = new ArrayList<>();
        for (int i = 0, len = nums.length; i < len; i++) {
            if (map.containsKey(nums[i]))
                map.put(nums[i], map.get(nums[i]) + 1);
            else
                map.put(nums[i], 1);
            if (map.get(nums[i]) > len / 3 && !ans.contains(nums[i]))
                ans.add(nums[i]);
        }
        return ans;
    }

方法二:排序比较

思路:

排序然后然后寻找大于1/3的数

大于1/3的数必然出现在1/6,1/2,5/6的位置,

所以我们记录此位置的数字,

然后只要记录出现次数,则可以知道大于1/3的元素

public List<Integer> majorityElement(int[] nums) {
        // 排序,然後计数(效率提高,内存消耗没有改变)
        List<Integer> ans = new ArrayList<>();
        if (nums.length == 0)
            return ans;
        Arrays.sort(nums);
        int len = nums.length;
        int A = nums[len / 6], B = nums[len / 2], C = nums[len * 5 / 6];
        int countA = 0, countB = 0, countC = 0;
        for (int i = 0; i < len; i++) {
            if (nums[i] == A)
                countA++;
            if (nums[i] == B)
                countB++;
            if (nums[i] == C)
                countC++;
            if (countA > len / 3 && !ans.contains(A))
                ans.add(A);
            if (countB > len / 3 && !ans.contains(B))
                ans.add(B);
            if (countC > len / 3 && !ans.contains(C))
                ans.add(C);
        }
        return ans;
    }

方法三:摩尔计数法

思路:

因为题目要求选出大于1/3的元素,

可知最多有两个元素

所以使用摩尔投票法,设置两个‘代表’

然后使用摩尔投票方法:

若是选择元素则加一,

若不是选择元素则减一,

若减至零,则换‘代表’

然后判断选择的代表是否全部满足要求

public List<Integer> majorityElement(int[] nums) {
        // 摩尔投票法
        List<Integer> ans = new ArrayList<>();
        int A = nums[0], B = nums[0];//设置候选组
        int countA = 0, countB = 0;
        for (int i = 0, len = nums.length; i < len; i++) {
            if (nums[i] == A) {//若当前值为A则加
                countA++;
            } else if (nums[i] == B) {
                countB++;
            } else if (countA == 0) {//若countA为零则换值
                A = nums[i];
                countA++;
            } else if (countB == 0) {
                B = nums[i];
                countB++;
            } else {
                countA--;
                countB--;
            }
        }
        countA = countB = 0;
        for (int i = 0, len = nums.length; i < len; i++) {//重新比较,是当前选择的元素是否大于1/3
            if (nums[i] == A) {
                countA++;
            }
            if (nums[i] == B) {
                countB++;
            }
            if (countA > len / 3 && !ans.contains(A)) {
                ans.add(A);
                continue;
            }
            if (countB > len / 3 && !ans.contains(B)) {
                ans.add(B);
                continue;
            }
        }
        return ans;
    }

11.(力扣练习)相对名次(map+排序)

给出 N 名运动员的成绩,找出他们的相对名次并授予前三名对应的奖牌。前三名运动员将会被分别授予 “金牌”,“银牌” 和“ 铜牌”(“Gold Medal”, “Silver Medal”, “Bronze Medal”)。

(注:分数越高的选手,排名越靠前。)

方法一:map+排序

思路:

先用map保存当前数组的相对位置,

在排序,然后根据排序后的位置,

用相对位置得到原来位置处的名次

public String[] findRelativeRanks(int[] nums) {
        // map+排序
        Map<Integer, Integer> map = new HashMap<>();
        int len = nums.length;
        String[] ans = new String[len];
        for (int i = 0; i < len; i++)
            map.put(nums[i], i);//保存排序前的相对位置
        Arrays.sort(nums);//排序
        int gold = 4;
        for (int i = len - 1; i >= 0; i--) {
            if (i == len - 1)
                ans[map.get(nums[i])] = "Gold Medal";//获取排序前的相对位置,然后保存在排序前的相对位置
            else if (i == len - 2)
                ans[map.get(nums[i])] = "Silver Medal";
            else if (i == len - 3)
                ans[map.get(nums[i])] = "Bronze Medal";
            else
                ans[map.get(nums[i])] = "" + gold++;
        }
        return ans;
    }

12.(力扣练习)有效的井字游戏(逻辑判断)

用字符串数组作为井字游戏的游戏板 board。当且仅当在井字游戏过程中,玩家有可能将字符放置成游戏板所显示的状态时,才返回 true。

该游戏板是一个 3 x 3 数组,由字符 " ",“X” 和 “O” 组成。字符 " " 代表一个空位。

以下是井字游戏的规则:

玩家轮流将字符放入空位(" ")中。
第一个玩家总是放字符 “X”,且第二个玩家总是放字符 “O”。
“X” 和 “O” 只允许放置在空位中,不允许对已放有字符的位置进行填充。
当有 3 个相同(且非空)的字符填充任何行、列或对角线时,游戏结束。
当所有位置非空时,也算为游戏结束。
如果游戏结束,玩家不允许再放置字符。

方法一:分类讨论

思路:

  • 当 X < O,X >= O + 2 返回false;
  • 当 X == O,判断X是否三连,是则返回false;
  • 当 X == O + 1,判断O是否三连,是则返回false;
  • 以上情况均不满足则返回true
public boolean validTicTacToe(String[] board) {
        
        int X = 0, O = 0;
        for (int i = 0, len = board.length; i < len; i++) {
            for (int j = 0; j < 3; j++) {
                char c = board[i].charAt(j);
                if (c == 'X')
                    X++;
                if (c == 'O')
                    O++;
            }
        }
        if (X < O)
            return false;
        if (X >= O + 2)
            return false;
        //判断是否只有唯一一种颜色三连
        if (X == O && judge(board, 'X'))
            return false;
        if (X == O + 1 && judge(board, 'O'))
            return false;
        return true;
    }

    private boolean judge(String[] board, char c) {
        if (board[0].charAt(0) == c &&
                board[0].charAt(1) == board[0].charAt(0) &&
                board[0].charAt(2) == board[0].charAt(0))
            return true;
        if (board[0].charAt(0) == c &&
                board[1].charAt(0) == board[0].charAt(0) &&
                board[2].charAt(0) == board[0].charAt(0))
            return true;
        if (board[0].charAt(0) == c &&
                board[1].charAt(1) == board[0].charAt(0) &&
                board[2].charAt(2) == board[0].charAt(0))
            return true;
        if (board[0].charAt(1) == c &&
                board[0].charAt(1) == board[1].charAt(1) &&
                board[2].charAt(1) == board[0].charAt(1))
            return true;
        if (board[0].charAt(2) == c &&
                board[0].charAt(2) == board[1].charAt(2) &&
                board[0].charAt(2) == board[2].charAt(2))
            return true;
        if (board[0].charAt(2) == c &&
                board[1].charAt(1) == board[0].charAt(2) &&
                board[0].charAt(2) == board[2].charAt(0))
            return true;
        if (board[1].charAt(0) == c &&
                board[1].charAt(1) == board[1].charAt(0) &&
                board[1].charAt(0) == board[1].charAt(2))
            return true;

        if (board[2].charAt(0) == c &&
                board[2].charAt(1) == board[2].charAt(0) &&
                board[2].charAt(0) == board[2].charAt(2))
            return true;
        return false;
    }

13.(力扣练习)翻转图像(模拟)

给定一个二进制矩阵 A,我们想先水平翻转图像,然后反转图像并返回结果。

水平翻转图片就是将图片的每一行都进行翻转,即逆序。例如,水平翻转 [1, 1, 0] 的结果是 [0, 1, 1]。

反转图片的意思是图片中的 0 全部被 1 替换, 1 全部被 0 替换。例如,反转 [0, 1, 1] 的结果是 [1, 0, 0]。

方法一:模拟

思路:

在原来数组上进行翻转和反转

把原来数组分成两边,两边对换,然后在对换过程中进行反转

public int[][] flipAndInvertImage(int[][] A) {
        // 原本数组上进行交换和翻转
        int len = A[0].length;
        for (int i = 0, size = A.length; i < size; i++) {
            for (int j = 0; j < (len + 1) / 2; j++) { // 取当前数组的一半
                int temp = A[i][j] ^ 1;// 对左边的元素进行反转
                A[i][j] = A[i][len - j - 1] ^ 1;// 交换两边元素,对右边元素进行反转
                A[i][len - j - 1] = temp;
            }
        }
        return A;
    }

14.(力扣练习)三数之和(双指针)

给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

方法一:回溯(超时)

public List<List<Integer>> threeSum(int[] nums) {
        //   回溯 超时
        List<List<Integer>> ans = new ArrayList<>();
        if (nums.length <= 2 || nums == null)
            return ans;
        Arrays.sort(nums);
        func(ans, new ArrayList<Integer>(), nums, 0);
        return ans;
    }

    private void func(List<List<Integer>> ans, ArrayList<Integer> list, int[] nums, int count) {
        if (list.size() == 3) {
            if (list.get(0) + list.get(1) + list.get(2) == 0)
                ans.add(new ArrayList<>(list));
            else
                return;
        } else {
            if (list.size() == 0 && nums[count] > 0)// 大于零则退出循环
                return;
            for (int i = count, len = nums.length; i < len; i++) {
                if (i > count && nums[i] == nums[i - 1])// 去重
                    continue;
                list.add(nums[i]);
                func(ans, list, nums, i + 1);
                list.remove(list.size() - 1);
            }
        }
    }

方法二:双指针

思路:

第一个指针 i 指向第一个数字,1.去重,2.保证第一个数非正数

第二个指针 L 指向第二个数字,1.内部去重

第三个指针 R 指向第三个数字,1.内部去重

public List<List<Integer>> threeSum(int[] nums) {
        // 双指针
        List<List<Integer>> ans = new ArrayList<>();
        if (nums.length <= 2 || nums == null)
            return ans;
        Arrays.sort(nums);
        for (int i = 0, len = nums.length; i < len; i++) {
            if (nums[i] > 0)// 大于零则退出
                break;
            if (i > 0 && nums[i] == nums[i - 1]) continue; // 外部去重
            int L = i + 1;
            int R = len - 1;
            while (L < R) {
                int sum = nums[i] + nums[L] + nums[R];
                if (sum == 0) {
                    ans.add(Arrays.asList(nums[i], nums[R], nums[L]));
                    // 内部去重
                    while (L < R && nums[L] == nums[L + 1]) L++;
                    while (L < R && nums[R] == nums[R - 1]) R--;
                    L++;
                    R--;
                }
                else if (sum < 0) L++;
                else if (sum > 0) R--;
            }
        }
        return ans;
    }

15.(力扣练习)四数之和(双指针)

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

注意:

答案中不可以包含重复的四元组。

方法一:双指针

思路:

与14题类似

public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> ans = new ArrayList<>();
        if (nums.length < 4 || nums == null)
            return ans;
        Arrays.sort(nums);//排序
        for (int i = 0, len = nums.length; i < len; i++) {//取第一个数字
            if (i > 0 && nums[i] == nums[i - 1])//第一次去重 去除与第一个数字相同的结果
                continue;
            for (int j = i + 1; j < len; j++) {//取第二个数字
                if (j > i + 1 && nums[j] == nums[j - 1])//第二次去重,去除与第二个数字相同的结果
                    continue;
                int L = j + 1;//取第三个数字
                int R = len - 1;//取第四个数字
                while (L < R) {//遍历内部所有情况
                    int sum = nums[i] + nums[j] + nums[L] + nums[R];
                    if (sum == target) {
                        ans.add(Arrays.asList(nums[i], nums[j], nums[L], nums[R]));//相等保存
                        while (L < R && nums[L] == nums[L + 1]) L++;//第三次去重,去重与第三个数字相同的结果
                        while (L < R && nums[R] == nums[R - 1]) R--;//第四次去重,去重与第四个数字相同的结果
                        L++;
                        R--;
                    } else if (sum < target) L++;
                    else if (sum > target) R--;
                }
            }
        }
        return ans;
    }

可以优化十秒

public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> ans = new ArrayList<>();
        if (nums.length < 4 || nums == null)
            return ans;
        Arrays.sort(nums);
        for (int i = 0, len = nums.length; i <= len - 4; i++) {
            if (nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target)//优化判断当前开始遍历情况最小值是否大于目标值
                break;
            if (i > 0 && nums[i] == nums[i - 1])
                continue;
            for (int j = i + 1; j <= len - 3; j++) {
                if (j > i + 1 && nums[j] == nums[j - 1])
                    continue;
                if (nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target)//优化遍历当前内部遍历情况最小值是否大于目标值
                    continue;
                int L = j + 1;
                int R = len - 1;
                while (L < R) {
                    int sum = nums[i] + nums[j] + nums[L] + nums[R];
                    if (sum == target) {
                        ans.add(Arrays.asList(nums[i], nums[j], nums[L], nums[R]));
                        while (L < R && nums[L] == nums[L + 1]) L++;
                        while (L < R && nums[R] == nums[R - 1]) R--;
                        L++;
                        R--;
                    } else if (sum < target) L++;
                    else if (sum > target) R--;
                }
            }
        }
        return ans;
    }

16.(蓝桥杯-算法训练)Bit Compressor(搜索)

数据压缩的目的是为了减少存储和交换数据时出现的冗余。这增加了有效数据的比重并提高了传输速率。有一种压缩二进制串的方法是这样的:
  加粗样式将连续的n个1替换为n的二进制表示(注:替换发生当且仅当这种替换减少了二进制串的总长度)
  (译者注:连续的n个1的左右必须是0或者是串的开头、结尾)
  比如:11111111001001111111111111110011会被压缩成10000010011110011。原串长为32,被压缩后串长为17.
  这种方法的弊端在于,有时候解压缩算法会得到不止一个可能的原串,使得我们无法确定原串究竟是什么。请你写一个程序来判定我们能否利用压缩后的信息来确定原串。给出原串长L,原串中1的个数N,以及压缩后的串。
  L<=16 Kbytes,压缩后的串长度<=40 bits。
输入格式
  第一行两个整数L,N,含义同问题描述
  第二行一个二进制串,表示压缩后的串
输出格式
  输出"YES"或"NO"或"NOT UNIQUE"(不包含引号)
  分别表示:
  YES:原串唯一
  NO:原串不存在
  NOT UNIQUE:原串存在但不唯一

方法一:dfs(借鉴:https://blog.csdn.net/yi_qing_z/article/details/88084875)

思路:

注意:1,10,11,110情况

    int L, l, N;
    char[] data = new char[50];
    int answer;

    public int fun(int L, int N, String str) {
        this.L = L;
        this.N = N;
        l = str.length();
        for (int i = 1; i < l + 1; i++)
            data[i] = str.charAt(i - 1);
        dfs(1, 0, 0);
        return answer;
    }

/*
     * i:指向短串位置的指针
     * length:解压后串的长度
     * num_1:解压后1的个数
     * */
    void dfs(int i, long length, long num_1) {      //i是原串第i个字符,length是变换后的串长,num_1是变换后串的1累计个数

        /*
         * 思路:
         * 连续n个1的压缩条件
         * 1.两头为开始,结束
         * 2.一头为开始或者结束,一头为0
         * 3.两头为0
         * 4.压缩长度必须大于2
         *   所以:1,10,11,110此类不能压缩
         * */
        if (i == l) {       //最后一个位置
            dfs(i + 1, length + 1, num_1 + data[i] - '0');
            return;
        }
        if (i > l) {   //结束条件
            if (length == L && num_1 == N)
                answer++;
            return;
        }
        if (length > L || num_1 > N || answer >= 2)    //不符合条件,返回
            return;
        /*
         * 1. 0   不解压
         * 2. 11  不解压
         * */
        if (data[i] == '0') {
            dfs(i + 1, length + 1, num_1);
            return;
        }
        if (data[i - 1] == '1')
            return;
        long temp = 0;
        /*
         * 10
         * 110
         * 可能解压
         * */
        if (data[i + 1] == '0')
            dfs(i + 1, length + 1, num_1 + 1);
        if (data[i + 1] == '1' && data[i + 2] != '1')
            dfs(i + 2, length + 2, num_1 + 2);
        for (int j = i; j <= l; j++) {
            temp *= 2;//从i开始转化二进制位10进制,计算1的数量
            temp += data[j] - '0';
            if (temp + num_1 > N || temp + length > L)
                break;
            if (temp > j - i + 1 && data[j + 1] != '1')        //压缩的串1比原来多,并且下一个位置为0或者末尾,代表转换结束
                dfs(j + 1, temp + length, num_1 + temp);
        }
    }

17.(蓝桥杯-算法训练)大小写转换(循环)

输入一个字符串,将大写字符变成小写、小写变成大写,然后输出

方法一:循环

public String changeString(String str) {
        String ans = "";
        for (int i = 0, len = str.length(); i < len; i++) {
            char c = str.charAt(i);
            if (c >= 'a' && c <= 'z')
                ans += (char) (c - 32);
            else if (c >= 'A' && c <= 'Z')
                ans += (char) (c + 32);
            else
                ans += c;
        }
        return ans;
    }

18.(蓝桥杯-算法训练)字符串合并

输入两个字符串,将其合并为一个字符串后输出。

方法一:

public String addString(String str1, String str2) {
        return str1 + str2;
    }

19.(蓝桥杯-算法训练) 大等于n的最小完全平方数(细心)

输出大等于n的最小的完全平方数。
若一个数能表示成某个自然数的平方的形式,则称这个数为完全平方数
Tips:注意数据范围

方法一:

思路:

  1. n<=0

  2. n==最小完全平方数

  3. n>最小完全平方数

  4. 注意范围,使用long保存数字

public static long getTheMinNum(long n) {
        if (n <= 0)
            return 0;
        long temp = (long) Math.sqrt(n);
        if (temp * temp == n)
            return n;
        temp++;
        return temp * temp;
    }

20.(力扣练习)在排序数组中查找元素的第一个和最后一个位置(二分)

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

你的算法时间复杂度必须是 O(log n) 级别。

如果数组中不存在目标值,返回 [-1, -1]。

方法一:二分

    //二分 最优
    public int[] searchRange(int[] nums, int target) {
        int[] ans = {-1, -1};
        int index = (0 + nums.length) / 2;
        if (nums.length != 0)
            find(ans, nums, target, index, 0, nums.length - 1);
        return ans;
    }

    private void find(int[] ans, int[] nums, int target, int index, int l, int r) {
        if (nums[index] == target) {//当前值等于目标值
            int left = index;
            int right = index;
            while (left >= 0 && nums[left] == target)
                left--;
            while (right <= nums.length - 1 && nums[right] == target)
                right++;
            ans[0] = ++left;
            ans[1] = --right;
            return;
        }
        if (l >= r) {//不满足条件
            return;
        }
        if (l < r) {
            if (nums[index] < target) {//当前值小于目标值
                find(ans, nums, target, (r + index + 1) / 2, index + 1, r);
            } else {//当前值大于目标值
                find(ans, nums, target, (l + index - 1) / 2, l, index - 1);
            }
        }
    }

方法二:双指针

思路:

线性搜索

// 双指针
    public int[] searchRange(int[] nums, int target) {
        int[] ans = {-1, -1};
        if (nums.length != 0) {
            int l = 0;
            int r = nums.length - 1;
            while (l <= r) {
                if (nums[l] == target)//左指针指向目标值
                    ans[0] = l;
                if (nums[r] == target)//右指针指向目标值
                    ans[1] = r;
                if (ans[0] == -1)//左指针移动
                    l++;
                if (ans[1] == -1)//右指针移动
                    r--;
                if (ans[0] != -1 && ans[1] != -1)
                    break;
            }
        }
        return ans;
    }

21.(力扣练习)腐烂的橘子(多源广度搜索)

在给定的网格中,每个单元格可以有以下三个值之一:

值 0 代表空单元格;
值 1 代表新鲜橘子;
值 2 代表腐烂的橘子。
每分钟,任何与腐烂的橘子(在 4 个正方向上)相邻的新鲜橘子都会腐烂。

返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1。

方法一:多源广度搜索

思路:

用队列保存以腐烂的橘子,

用键值对保存橘子开始腐烂的

    int[] change_I = {1, 0, -1, 0};
    int[] change_J = {0, 1, 0, -1};

    public int orangesRotting(int[][] grid) {
        int ans = 0;
        int I = grid.length;
        int J = grid[0].length;
        Map<Integer, Integer> map = new HashMap<>();
        Queue<Integer> queue = new ArrayDeque<>();
        for (int i = 0; i < I; i++)
            for (int j = 0; j < J; j++) {
                if (grid[i][j] == 2) {
                    int index = i * J + j;//记录位置
                    queue.add(index);
                    map.put(index, 0);//记录当前位置腐败时间
                }
            }
        while (!queue.isEmpty()) {
            int index = queue.remove();
            int i = index / J;
            int j = index % J;
            for (int k = 0; k < 4; k++) {
                int newI = i + change_I[k];
                int newJ = j + change_J[k];
                if (0 <= newI && newI < I && 0 <= newJ && newJ < J && grid[newI][newJ] == 1) {
                    grid[newI][newJ] = 2;
                    int newIndex = newI * J + newJ;//保存下一个腐烂的橘子
                    queue.add(newIndex);
                    int time = map.get(index) + 1;
                    map.put(newIndex, time);
                    ans = time;
                }
            }
        }
        for (int[] i : grid)//查询是否有未腐烂的橘子
            for (int j : i)
                if (j == 1)
                    return -1;
        return ans;
    }

22.(力扣练习)将数组分成和相等的三个部分(双指针)

给你一个整数数组 A,只有可以将其划分为三个和相等的非空部分时才返回 true,否则返回 false。

形式上,如果可以找出索引 i+1 < j 且满足 (A[0] + A[1] + … + A[i] == A[i+1] + A[i+2] + … + A[j-1] == A[j] + A[j-1] + … + A[A.length - 1]) 就可以将数组三等分。

方法一:双指针

思路:

计算出平均值,指定两个指针,一个从前,一个从后,进行检索,知道分别满足条件,及分成三分

public boolean canThreePartsEqualSum(int[] A) {
        int len = A.length;
        if (len < 3)//长度小于3返回false
            return false;
        int sum = 0;
        for (int i = 0; i < len; i++)
            sum += A[i];//求总长
        if (sum % 3 != 0)
            return false;
        int l = 0;
        int r = len - 1;
        int average = sum / 3;
        int averageL = A[l];
        int averageR = A[r];
        while (l < r) {
            if (averageL == average && averageR == average && l + 1 < r) //满足条件返回true
                return true;
            //两边开始遍历,找到平均节点
            if (averageL == average)
                averageR += A[--r];
            else if (averageR == average)
                averageL += A[++l];
            else {
                averageR += A[--r];
                averageL += A[++l];
            }

        }
        return false;
    }

23.(力扣练习)多数元素(摩尔投票法)

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

方法一:排序

思路:先排序,然后取中值

//排序 2ms 41.9MB
    public int majorityElement(int[] nums) {
        Arrays.sort(nums);
        return nums[nums.length / 2];
    }

方法二:摩尔投票法

思路:使用一个临时变量保存当前值的个数,当为零的时候换下一个数重新开始计数

//摩尔投票法 3ms 42.1MB
   public int majorityElement(int[] nums) {
        int ans = nums[0];
        int len = nums.length;
        int count = 1;//计数
        int i = 1;
        while (count <= len / 2 && i < len) {
            if (nums[i] != ans) {//不等于当前值
                count--;
            } else {//等于当前值
                count++;
            }
            if (count == 0) {//当计数器为零时
                ans = nums[i];
                count++;
            }
            i++;
        }
        return ans;
    }

24.(力扣练习)零钱兑换(动态规划)

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

方法一:动态规划(自下而上)

思路:

public int coinChange(int[] coins, int amount) {
        int len = coins.length;
        int[] dp = new int[amount + 1];
        dp[0] = 0;
        for (int i = 1; i <= amount; i++) {
            int min = -1;
            for (int j = 0; j < len; j++) {
                int index = i - coins[j];
                if (index >= 0 && dp[index] != -1) {
                    if (min == -1)
                        min = dp[index] + 1;
                    else
                        min = Math.min(min, dp[index] + 1);
                } else
                    continue;
            }
            dp[i] = min;
        }
        return dp[amount];
    }

25.(力扣练习)最长上升子序列(动态规划)

给定一个无序的整数数组,找到其中最长上升子序列的长度。

方法一:动态规划

思路:

public int lengthOfLIS(int[] nums) {
        int ans = 0;
        int len = nums.length;
        int[] dp = new int[len];
        if (len != 0)
            dp[0] = ans = 1;
        else
            return ans;
        for (int i = 1; i < len; i++) {
            int max = 1;
            for (int j = 0; j < i; j++) {
                if (nums[j] < nums[i])
                    max = Math.max(max, dp[j] + 1);
            }
            dp[i] = max;
            ans = ans > max ? ans : max;
        }
        return ans;
    }

26.(力扣练习)岛屿的最大面积(广度搜索)

给定一个包含了一些 0 和 1的非空二维数组 grid , 一个 岛屿 是由四个方向 (水平或垂直) 的 1 (代表土地) 构成的组合。你可以假设二维矩阵的四个边缘都被水包围着。

找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为0。

方法一:广度搜索

思路:

    int[] changeX = {1, 0, -1, 0};
    int[] changeY = {0, 1, 0, -1};

    public int maxAreaOfIsland(int[][] grid) {
        int ans = 0;
        Queue<Integer> queue = new ArrayDeque<>();
        int X = grid.length;
        int Y = grid[0].length;
        for (int i = 0; i < X; i++) {
            for (int j = 0; j < Y; j++) {
                if (grid[i][j] == 1) {
                    int max = 1;
                    grid[i][j] = 0;
                    int index = i * Y + j;
                    queue.add(index);
                    while (!queue.isEmpty()) {
                        int newIndex = queue.poll();
                        for (int k = 0; k < 4; k++) {
                            int newX = newIndex / Y + changeX[k];
                            int newY = newIndex % Y + changeY[k];
                            if (0 <= newX && newX < X && 0 <= newY && newY < Y && grid[newX][newY] == 1) {
                                max++;
                                queue.add(newX * Y + newY);
                                grid[newX][newY] = 0;
                            }
                        }
                    }
                    ans = ans > max ? ans : max;
                }
            }
        }
        return ans;
    }

27.(力扣练习)有效括号的嵌套深度(对题目的理解)

有效括号字符串 仅由 “(” 和 “)” 构成,并符合下述几个条件之一:

空字符串
连接,可以记作 AB(A 与 B 连接),其中 A 和 B 都是有效括号字符串
嵌套,可以记作 (A),其中 A 是有效括号字符串
类似地,我们可以定义任意有效括号字符串 s 的 嵌套深度 depth(S):

s 为空时,depth(“”) = 0
s 为 A 与 B 连接时,depth(A + B) = max(depth(A), depth(B)),其中 A 和 B 都是有效括号字符串
s 为嵌套情况,depth(“(” + A + “)”) = 1 + depth(A),其中 A 是有效括号字符串
例如:“”,“()()”,和 “()(()())” 都是有效括号字符串,嵌套深度分别为 0,1,2,而 “)(” 和 “(()” 都不是有效括号字符串。

给你一个有效括号字符串 seq,将其分成两个不相交的子序列 A 和 B,且 A 和 B 满足有效括号字符串的定义(注意:A.length + B.length = seq.length)。

现在,你需要从中选出 任意 一组有效括号字符串 A 和 B,使 max(depth(A), depth(B)) 的可能取值最小。

返回长度为 seq.length 答案数组 answer ,选择 A 还是 B 的编码规则是:如果 seq[i] 是 A 的一部分,那么 answer[i] = 0。否则,answer[i] = 1。即便有多个满足要求的答案存在,你也只需返回 一个。

示例 1:

输入:seq = “(()())”
输出:[0,1,1,1,1,0]
示例 2:

输入:seq = “()(())()”
输出:[0,0,0,1,1,0,1,1]

方法一:找规律

思路:

把括号分成两部分,每部分最深层数只有一层->根据括号位置,给括号编号,奇数为1,偶数为0

public int[] maxDepthAfterSplit(String seq) {
        int[] ans = new int[seq.length()];
        int idx = 0;
        for (char c : seq.toCharArray()) {
            /*
             * 对于‘(’奇数在一层,偶数在零层
             * 对于‘)’相对左括号想右偏移一,所以判断层数时加一
             */
            ans[idx++] = c == '(' ? idx & 1 : ((idx + 1) & 1);
        }
        return ans;
    }

28.(力扣练习)机器人的运动范围(广度搜索)

地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),

也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,

因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?

示例 1:

输入:m = 2, n = 3, k = 1
输出:3
示例 1:

输入:m = 3, n = 1, k = 0
输出:1

提示:

  • 1 <= n,m <= 100
  • 0 <= k <= 20

方法一:广度搜索

思路:

因为机器人不能走横纵坐标超过k的格子,

所以题目转化为->机器人所能走的最大格子数

所以先获取机器人所能走的格子

然后使用广度搜索模板即可

public int movingCount(int m, int n, int k) {
        int ans = 0;
        boolean[][] map = new boolean[m][n];
        int[] changeX = {0, 1, 0, -1};
        int[] changeY = {1, 0, -1, 0};
        // 获取机器人所能走的格子
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (getSum(i) + getSum(j) <= k) {
                    map[i][j] = true;
                } else {
                    map[i][j] = false;
                }
            }
        }
       // 广度搜索模板
        Queue<Integer> queue = new ArrayDeque<>();
        ans++;
        map[0][0] = false;
        queue.add(0);
        while (!queue.isEmpty()) {
            int index = queue.poll();
            int x = index / n;
            int y = index % n;
            for (int count = 0; count < 4; count++) {
                int nx = x + changeX[count];
                int ny = y + changeY[count];
                if (0 <= nx && nx < m && 0 <= ny && ny < n && map[nx][ny]) {
                    map[nx][ny] = false;// 当走过后记录,以免重复搜索
                    ans++;
                    queue.add(nx * n + ny);
                }
            }
        }
        return ans;
    }

    private int getSum(int num) {
        int sum = 0;
        while (num != 0) {
            sum += num % 10;
            num = num / 10;
        }
        return sum;
    }
public int movingCount(int m, int n, int k) {
	// 优化,因为要遍历全部路径,所以其实机器上只需要从左下角走完所有的可选择路径即可
        // 即:只需要走右上,即可
        int ans = 0;
        int[] changeX = {0, 1};
        int[] changeY = {1, 0};
        int[][] map = new int[m][n]; //默认全部为走过
        Queue<Integer> queue = new ArrayDeque<>();
        ans++;
        map[0][0] = 1;// 走过做标记
        queue.add(0);
        while (!queue.isEmpty()) {
            int index = queue.poll();
            int x = index / n;
            int y = index % n;
            for (int count = 0; count < 2; count++) {
                int nx = x + changeX[count];
                int ny = y + changeY[count];
                if (nx < m && ny < n && getSum(nx) + getSum(ny) <= k && map[nx][ny] == 0) {// 判断是否允许走入
                    map[nx][ny] = 1;
                    ans++;
                    queue.add(nx * n + ny);
                }
            }
        }
        return ans;
    }

    private int getSum(int num) {
        int sum = 0;
        while (num != 0) {
            sum += num % 10;
            num = num / 10;
        }
        return sum;
    }

29.(力扣练习) 翻转字符串里的单词(双指针,API)

给定一个字符串,逐个翻转字符串中的每个单词

示例 1:

输入: “the sky is blue”
输出: “blue is sky the”
示例 2:

输入: " hello world! "
输出: “world! hello”
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:

输入: “a good example”
输出: “example good a”
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

说明:

无空格字符构成一个单词。
输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

方法一:调用AIP

思路:

先句子首尾去空白,然后分割,然后倒置输出

用时:279ms,内存消耗: 40.8MB

public String reverseWords(String s) {
        String ns = "", ans = "";
        // 去除多余的空格
        for (int i = 0, len = s.length(); i < len; i++) {
            char c = s.charAt(i);
            if (i == 0 && c == ' ') {
                while (i < len && s.charAt(i) == ' ') {
                    i++;
                }
                i--;
                continue;
            }
            if (i > 0 && c == ' ' && s.charAt(i - 1) == ' ') {
                continue;
            }
            ns += c;
        }
        String[] strings = ns.split(" ");
        for (int i = strings.length - 1; i >= 0; i--) {
            ans += i == 0 ? strings[i] : strings[i] + " ";
        }
        return ans;
    }

方法一优化:

用时7ms,内存消耗: 39.9MB

public String reverseWords(String s) {
        // 除去开头和末尾的空白字符
        s = s.trim();
        // 正则匹配连续的空白字符作为分隔符分割
        List<String> wordList = Arrays.asList(s.split("\\s+"));
        Collections.reverse(wordList);
        return String.join(" ", wordList);
    }

方法二:双指针

用时:3ms,内存消耗:40.1MB

思路:

句子首尾去空白,然后倒置查询,然后使用两个指针指向单词首尾

ps:这里使用StringBuffer保存比使用String保存更快

String保存:用时:15ms,内存消耗:40.1MB

public String reverseWords(String s) {
        // 除去开头和末尾的空白字符
        s = s.trim();
        StringBuffer sb = new StringBuffer();
        // i指向单词开始,j指向单词结束
        int i = s.length() - 1, j = i;
        while (i >= 0) {
            // 读取单词开头
            while (i >= 0 && s.charAt(i) != ' ') {
                i--;
            }
            sb.append(s.substring(i + 1, j + 1) + " ");
            // 跳过空白
            while (i >= 0 && s.charAt(i) == ' ') {
                i--;
            }
            j = i;
        }
        return sb.toString().trim();
    }

30.(力扣练习)两数相加 II(栈,头插法)

给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。

你可以假设除了数字 0 之外,这两个数字都不会以零开头。

进阶:

如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。

示例:

输入:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 8 -> 0 -> 7

方法一:栈

思路:

把两个链表存入栈中,从末尾读取,然后使用头插法,保存到head链表中

public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        Stack<ListNode> stack1 = new Stack<>();
        Stack<ListNode> stack2 = new Stack<>();
        while (l1 != null) {
            stack1.add(l1);
            l1 = l1.next;
        }
        while (l2 != null) {
            stack2.add(l2);
            l2 = l2.next;
        }
        int num = 0;
        ListNode head = null;
        while (!stack1.isEmpty() || !stack2.isEmpty() || num > 0) {
            int sum = num;
            sum += stack1.isEmpty() ? 0 : stack1.pop().val;
            sum += stack2.isEmpty() ? 0 : stack2.pop().val;
            // 头插法
            ListNode node = new ListNode(sum % 10);
            node.next = head;
            head = node;
            num = sum / 10;
        }
        return head;
    }
class ListNode {
    public int val;
    public ListNode next;

    public ListNode(int x) {
        val = x;
    }
}

31.(力扣练习)除自身以外数组的乘积(左右乘积列表)

给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。

示例:

输入: [1,2,3,4]
输出: [24,12,8,6]

提示:题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。

说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。

进阶:
你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)

方法一:左右乘积列表

思路:

用两个列表保存从左到右,和从右到左的乘积和

保存方式为错位保存

public int[] productExceptSelf(int[] nums) {
        // 获取当前数组长度
        int len = nums.length;
        // 定义返回数组,因为题目说明大小小于int所以可以直接使用int类型
        int[] res = new int[len];
        int l = 1, r = 1;
        for (int i = 0; i < len; i++) {
            // 初始化 对数据进行赋值
            // 错位保存,不保存当前值,保存前一个值
            res[i] = l;
            l *= nums[i];
        }
        for (int i = len - 1; i >= 0; i--) {
            // 对数据进行二次赋值
            res[i] *= r;
            r *= nums[i];
        }
        return res;
    }

32(力扣练习)顺时针打印矩阵(深度搜索)

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

示例 1:

输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
示例 2:

输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]

限制:

0 <= matrix.length <= 100
0 <= matrix[i].length <= 100
注意:本题与主站 54 题相同:https://leetcode-cn.com/problems/spiral-matrix/

方法一:深度搜索

思路:

每次按一个方向搜索,达到结束标志的时候换方向

结束标志:超出界限或者被访问过

int[] changeX = {0, 1, 0, -1};
    int[] changeY = {1, 0, -1, 0};
    int X, Y;
    boolean[][] flag;

    public int[] spiralOrder(int[][] matrix) {
        X = matrix.length;
        if (X == 0) {
            return new int[0];
        }
        Y = matrix[0].length;
        if (Y == 0) {
            return new int[0];
        }
        int[] re = new int[X * Y];
        flag = new boolean[X][Y];
        dfs(re, matrix, 0, 0, 0, 0);
        return re;
    }

    private boolean dfs(int[] re, int[][] matrix, int x, int y, int k, int index) {
        // 判断是否到结束值
        if (x < 0 || x >= X || y < 0 || y >= Y || flag[x][y]) {
            return false;
        }
        re[index] = matrix[x][y];
        flag[x][y] = true;
        int nx = x + changeX[k], ny = y + changeY[k];
        // 访问玩该方向所有值
        if (dfs(re, matrix, nx, ny, k, index + 1)) {
            return true;
        }
        // 换方向
        k = (k + 1) % 4;
        nx = x + changeX[k];
        ny = y + changeY[k];
        return dfs(re, matrix, nx, ny, k, index + 1);
    }

33(力扣练习)种花问题

假设有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花不能种植在相邻的地块上,它们会争夺水源,两者都会死去。

给你一个整数数组 flowerbed 表示花坛,由若干 0 和 1 组成,其中 0 表示没种植花,1 表示种植了花。另有一个数 n ,能否在不打破种植规则的情况下种入 n 朵花?能则返回 true ,不能则返回 false。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/can-place-flowers
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

示例 1:

输入:flowerbed = [1,0,0,0,1], n = 1
输出:true
示例 2:

输入:flowerbed = [1,0,0,0,1], n = 2
输出:false


思路:

1.找到字符串中的连续0然后+1除以2表示该连续字符串中最多可种花数目

2.寻找连续零字符串:若当前为0则记录当前0个数,若当前为1这清空当前记录数字,并计算当前最多可以种多少数字

    public boolean canPlaceFlowers(int[] flowerbed, int n) {
        int ans = 0, len = flowerbed.length, i = 0;// ans 计算当前共可种多少花
        int k = 0;// k计数当前可种花数目
        while (i < len) {
            if (ans >= n) return true; // 若当前种花数已经满足要求则返回true
            if (flowerbed[i] == 1) {// 若当前格子有花则计算之前可种花数目,并跳到后一个重新计数
                i = i + 2;
                k--;
                ans += (k + 1) / 2;
                k = 0;
            } else { // 若未种花则统计计数
                k++;
                i++;
            }
        }
        ans += (k + 1) / 2;
        return ans >= n;
    }

34(力扣练习)等式方程的可满足性(并查集)

给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 equations[i] 的长度为 4,并采用两种不同的形式之一:“a==b” 或 “a!=b”。在这里,a 和 b 是小写字母(不一定不同),表示单字母变量名。

只有当可以将整数分配给变量名,以便满足所有给定的方程时才返回 true,否则返回 false。

示例 1:

输入:[“a==b”,“b!=a”]
输出:false
解释:如果我们指定,a = 1 且 b = 1,那么可以满足第一个方程,但无法满足第二个方程。没有办法分配变量同时满足这两个方程。

示例 2:

输入:[“ba","ab”]
输出:true
解释:我们可以指定 a = 1 且 b = 1 以满足满足这两个方程。

示例 3:

输入:[“ab","bc”,“a==c”]
输出:true

示例 4:

输入:[“ab",“b!=c”,"ca”]
输出:false

示例 5:

输入:[“cc","bd”,“x!=z”]
输出:true

提示:

1 <= equations.length <= 500
equations[i].length == 4
equations[i][0] 和 equations[i][3] 是小写字母
equations[i][1] 要么是 ‘=’,要么是 ‘!’
equations[i][2] 是 ‘=’

思路

上面提到的问题是求解变量之间关系的方程组。可以使用并查集来解决这个问题。

并查集是一种数据结构,它可以用来维护一组不相交的集合,并支持两个基本操作:

  • 合并:将两个不相交的集合合并为一个集合。
  • 查询:查询元素所属的集合。

对于给定的方程组,可以通过以下步骤来解决问题:

  1. 创建一个并查集。
  2. 对于每个方程,如果它表示变量之间的相等关系,则将两个变量所在的集合合并。
  3. 如果某个方程表示变量之间的不等关系,则检查两个变量是否在同一集合中,如果是,则说明方程组无解,直接返回 false。
  4. 遍历所有方程后,如果所有变量都在同一个集合中,则说明方程组有解,返回 true。

并查集模板

type UnionFind struct {
	parent []int
}

// MakeSet
// 初始化并查集
func (u *UnionFind) MakeSet(n int) {
	u.parent = make([]int, n)
	for i := 0; i < n; i++ {
		u.parent[i] = i
	}
}

// 如果只是查询一次,那么 Find 与其他优化查询没区别
// 如果生成后会查询多次,则最好使用优化查询

// Find
// 查询
func (u *UnionFind) Find(x int) int {
	if u.parent[x] != x {
		u.parent[x] = u.Find(u.parent[x])
	}
	return u.parent[x]
}

// FindByPathCompression
// 路径压缩优化
// 查询后会指向最后的值
func (u *UnionFind) FindByPathCompression(x int) int {
	if u.parent[x] != x {
		u.parent[x] = u.FindByPathCompression(u.parent[x])
		u.parent[x] = u.parent[u.parent[x]]
	}
	return u.parent[x]
}

// FindBypathSplit
// 路径分裂
func (u *UnionFind) FindBypathSplit(x int) int {
	if u.parent[x] != x {
		root := u.FindBypathSplit(x)
		u.parent[x] = root
	}
	return u.parent[x]
}

// Union
// 联合
func (u *UnionFind) Union(x, y int) {
	tx, ty := u.Find(x), u.Find(y)
	//tx, ty := u.FindByPathCompression(x), u.FindByPathCompression(y)
	if tx != ty {
		u.parent[tx] = ty
	}
}

实现

// 并查集
func equationsPossible(equations []string) bool {
	parent := make([]int, 26)
	// 初始化
	for i := 0; i < 26; i++ {
		parent[i] = i
	}

	for _, equation := range equations {
		if equation[1] == '=' {
			x, y := int(equation[0]-'a'), int(equation[3]-'a')
			union(parent, x, y)
		}else if equation[1] == '!' {
			x, y := int(equation[0]-'a'), int(equation[3]-'a')
			if find(parent, x) == find(parent, y) {
				return false
			}
		}
	}

	return true
}

func union(parent []int, x, y int) {
	parent[find(parent, x)] = find(parent, y)
}

func find(parent []int, idx int) int {
	for parent[idx] != idx {
    // 路径压缩
		parent[idx] = parent[parent[idx]]
		idx = parent[idx]
	}
	return idx
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值