算法练习---蓝桥杯 && 力扣(更新中ING)

算法练习
算法练习
1.(力扣练习)最大连续数字組(动态规划)
2.完全背包问题(动态规划)
3.扔鸡蛋问题(动态规划)
4.(力扣练习)比特位计数(技巧)
5.(力扣练习)除自身以外数组的乘积(技巧)
6.(力扣练习)寻找重复数(三方法)
7.(力扣练习)颜色分类(两方法)
8.(力扣练习)房间与钥匙(DFS)
9.(力扣练习)使用最小花费爬楼梯(动态规划)
10.(力扣练习)求众数 II(三方法)
11.(力扣练习)相对名次(map+排序)
12.(力扣练习)有效的井字游戏(逻辑判断)
13.(力扣练习)翻转图像(模拟)
14.(力扣练习)三数之和(双指针)
15.(力扣练习)四数之和(双指针)
16.(蓝桥杯-算法训练)Bit Compressor(搜索)
17.(蓝桥杯-算法训练)大小写转换(循环)
18.(蓝桥杯-算法训练)字符串合并
19.(蓝桥杯-算法训练) 大等于n的最小完全平方数(细心)
20.(力扣练习)在排序数组中查找元素的第一个和最后一个位置(二分)
21.(力扣练习)腐烂的橘子(多源广度搜索)
22.(力扣练习)将数组分成和相等的三个部分(双指针)
23.(力扣练习)多数元素(摩尔投票法)
24.(力扣练习)零钱兑换(动态规划)
25.(力扣练习)最长上升子序列(动态规划)
26.(力扣练习)岛屿的最大面积(广度搜索)
27.(力扣练习)有效括号的嵌套深度(对题目的理解)
28.(力扣练习)机器人的运动范围(广度搜索)
29.(力扣练习) 翻转字符串里的单词(双指针,API)
30.(力扣练习)两数相加 II(栈,头插法)
31.(力扣练习)除自身以外数组的乘积(左右乘积列表)
32(力扣练习)顺时针打印矩阵(深度搜索)

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}

0 1 2 3 4 5 6 7
0 0 0 0 0 0 0 0 0
1 0 0 0 dp[1,3]=max(dp[0,3],dp[1,3-w[1]]+v[1])=4 4 4 dp[1,6]=max(dp[0,6],dp[1,6-w[1]2]+v[1]2)=8 8
2 0 0 0 4 dp[2,4]=max(dp[1,4],dp[2,4-w[2]]+v[2])=5 5 8 dp[2,7]=max(dp[1,7],dp[2,7-w[2]]+v[2])=9
3 0 0 3 4 dp[3,4]=max(dp[2,4],dp[3,4-w[3]2]+v[3]2)=6 dp[3,5]=max(dp[2,5],dp[3,5-w[3]]+v[3])=7 9 10
動態轉移方程式:

							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层开始

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

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

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

若硬度大于等于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 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],每个钥匙 roomsi 由 [0,1,…,N-1] 中的一个整数表示,其中 N = rooms.length。

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

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

方法一:DFS

思路:

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

public boolean canVisitAllRooms(List 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> 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 majorityElement(int[] nums) {
//原始方法,使用map计数(效率低,内存消耗高,书写简单)
Map map = new HashMap<>();
List 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 majorityElement(int[] nums) {
// 排序,然後计数(效率提高,内存消耗没有改变)
List 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©)
ans.add©;
}
return ans;
}
方法三:摩尔计数法

思路:

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

可知最多有两个元素

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

然后使用摩尔投票方法:

若是选择元素则加一,

若不是选择元素则减一,

若减至零,则换‘代表’

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

public List majorityElement(int[] nums) {
// 摩尔投票法
List 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 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 threeSum(int[] nums) {
// 回溯 超时
List ans = new ArrayList<>();
if (nums.length <= 2 || nums == null)
return ans;
Arrays.sort(nums);
func(ans, new ArrayList(), nums, 0);
return ans;
}

private void func(List> ans, ArrayList 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 threeSum(int[] nums) {
// 双指针
List 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 fourSum(int[] nums, int target) {
List 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 fourSum(int[] nums, int target) {
List 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:注意数据范围

方法一:

思路:

n<=0

n==最小完全平方数

n>最小完全平方数

注意范围,使用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 map = new HashMap<>();
    Queue 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 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 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 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 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 stack1 = new Stack<>();
Stack 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);
}

你可能感兴趣的:(算法,蓝桥杯,力扣)