题目:
1. 第79题 单词搜索
给定一个二维网格和一个单词,找出该单词是否存在于网格中。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
一道搜索题目,只需要枚举起点,然后dfs一遍看看能不能搜到该单词的路径即可。
时间复杂度应该是n^2
个起点,单词长度为k = 1000
,除了第一次向四个方向搜索外,其余向三个方向搜索(因为不能重复使用某个单元格),搜索次数3^k
,总体就是n^2 * 3^k
,但是搜索题吗,看复杂度就没有能AC的题目了。
具体看代码吧
class Solution {
// 方向向量,向四个方向走
int[] dx = {
-1, 0, 1, 0}, dy = {
0, 1, 0, -1};
boolean find(char[][] board, String word, int u, int x, int y) {
// 如果当前格子不对应word的第u个字母,那就没必要往下搜索了,这条路径肯定找不到答案
if (board[x][y] != word.charAt(u)) return false;
// 如果这格子对应了最后一个字母,且执行到这,那说明最后一个字母也一样,那就找到了,返回即可
if (u == word.length() - 1) return true;
// 记录并且改变当前格子的字母,改成一个不可能出现的字符即可,防止后续搜索回来,也可以用visit数组记录一下
char t = board[x][y];
board[x][y] = '.';
for (int i = 0; i < 4; i ++ ) {
// 下一个格子的位置
int a = x + dx[i], b = y + dy[i];
// 不能越界,不能往回走
if (a < 0 || a >= board.length || b < 0 || b >= board[0].length || board[a][b] == '.') continue;
// 如果找到了,返回true
if (find(board, word, u + 1, a, b)) return true;
}
// 回溯
board[x][y] = t;
// 四个方向都找过了还没有返回true,拿当前起点行不通
return false;
}
public boolean exist(char[][] board, String word) {
int n = board.length, m = board[0].length;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ )
if (find(board, word, 0, i, j)) return true;
return false;
}
}
2. 第80题 删除排序数组中的重复项 II
给定一个增序排列数组 nums ,你需要在 原地 删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
很烦,原地修改很简单,但是它要求在O(1)
的额外空间完成。不然我直接就是一个Map
存次数,就AC了。
现在我们需要借助题中给的一个条件增序,如果不是有序的下面这做法还不行。
开一个变量k
记录当前目标数组中的个数,一开始没遍历肯定是0,然后扫描整个数组,原数组有序,那目标数组肯定也是有序的。每个元素最多出现两次,这时候就算重复,那因为整个数组有序,一定是目标数组当前最后的两个元素,只要这俩元素不跟它一样,那把它算进去一定不会超过两次,否则如果最后两个元素都跟它一样,那就肯定不能加到目标数组中。
class Solution {
public int removeDuplicates(int[] nums) {
int k = 0;
for (int i = 0; i < nums.length; i ++ ) {
// k < 2说明目标元素一共不到两个,肯定可以往目标数组中放的
// 这个小括号只是为了思路清晰~
if (k < 2 || (nums[i] != nums[k - 1] || nums[i] != nums[k - 2])) nums[k ++ ] = nums[i];
}
return k;
}
}
3. 第81题 搜索旋转排序数组 II
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。
编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。
题意就是找一个数组中是否有某个数,直接扫描一遍就行了
class Solution {
public boolean search(int[] nums, int target) {
for (int i = 0; i < nums.length; i ++ )
if (nums[i] == target) return true;
return false;
}
}
注意:这复杂度已经是最好的了,为O(n),是第33题的延申,33题可以用二分来做,复杂度是O(logn),但是这道题目就算二分复杂度也是O(n)的,具体做法为将右侧的跟nums[0]相同的数全部删掉,这样就满足二段性,和33题一样了
class Solution {
public boolean search(int[] nums, int target) {
int n = nums.length - 1;
while (n >= 0 && nums[n] == nums[0]) n -- ; // 将右侧和第一个元素相同的删掉
if (n < 0) return nums[0] == target; // 如果删光了,那就是整个数组的数都一样,看看是不是targe即可
// 下面就是33题的代码,第一次二分找到旋转点,第二次二分找到target
int l = 0, r = n;
while (l < r) {
int mid = l + r + 1 >> 1;
if (nums[mid] >= nums[0]) l = mid;
else r = mid - 1;
}
if (target < nums[0]) {
// 如果target < nums[0],说明目标值只能在分界点的右侧
l ++ ;
r = n;
} else {
r = l;
l = 0;
}
while (l < r) {
int mid = l + r >> 1;
if (nums[mid] >= target) r = mid;
else l = mid + 1;
}
return target == nums[r];
}
}
4. 第82题 删除排序链表中的重复元素 II
给定一个
排序链表
,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。
一道链表题,画个图整理下思路就出来了。注意指针别乱就行
因为头节点也可能被删除,所以为了方便建立一个虚拟头节点dummy
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode dummy = new ListNode(-1, head);
ListNode p = dummy; // 因为并不知道一个值是多个数还是一个数,所以p永远指向已经完成的最后一个,这样方便修改next指针,就像上面3重复,p指向的是3前面的2
while (p.next != null) {
// 后面还有值
ListNode q = p.next.next; // 第二个3的位置,就是有可能会和p.next重复的第一个数
while (q != null && q.val == p.next.val) q = q.next;
if (p.next.next == q) p = p.next; // 如果两个挨着,那就说明p.next只有自己,可用,移动位置
else p.next = q; // 否则就是有重复元素,中间这段都不要,直接指针指向q,但是不要移动,就是上图直接2指向4即可
}
return dummy.next; // 返回真正头节点
}
}
5. 第83题 删除排序链表中的重复元素
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null) return head; // 真实LC特色,这地方也能坑
ListNode p = head;
while (p.next != null) {
if (p.val == p.next.val) p.next = p.next.next; // 直接指向后面的元素就当于删除了
else p = p.next;
}
return head;
}
}
6. 第84题 柱状图中最大的矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
可以,难题。用常规思路的话就是枚举左右端点l, r
,然后从l
遍历到r
,找到里面最低的柱子高度h
,面积就是h * (r - l + 1)
,所有面积max就是答案。复杂度很高n^3
。(暴力写法就不写了,笔试真不会了那我肯定就暴力try了)
可以换一种思想,不去枚举左右端点,而去枚举最低高度,这样计算方法不变,需要求什么呢?
需要求出左右各自第一个比他矮的,因为比他高的可以配合它形成矩形,而矮的不可以。
单调栈正好解决此问题,单调栈可以在线性复杂度求出上述问题。
思想:假设从左向右扫描,找左面第一个比自己矮的,那如果栈内的元素都比自己大会是什么情况?自己在栈内的元素的右侧(因为从左向右,并且进栈都是扫描过的),且比栈内元素小,意味着栈内所有比自己大的数都有没有用了,因为后面的元素假设为x再来看这个栈的时候,如果前面那个数比x小,那当前元素一定比x小,且离x更近,那到当前元素就应该停止了,而非前面的元素。这样每次都把比自己大的元素弹出去了,剩下的都比自己小,依次类推,整个栈是依次递增的,所以叫单调栈。
class Solution {
public int largestRectangleArea(int[] heights) {
int n = heights.length;
int[] l = new int[n];
int[] r = new int[n];
Stack<Integer> stk = new Stack();
for (int i = 0; i < heights.length; i ++ ) {
// 把之前扫描过的且后面用不上的元素全部弹出去
while (!stk.empty() && heights[stk.peek()] >= heights[i]) stk.pop();
// 没有元素了说明前面没有比当前元素小的
if (stk.empty()) l[i] = -1;
else l[i] = stk.peek();
// 放入当前的元素索引
stk.push(i);
}
stk.clear();
for (int i = heights.length - 1; i >= 0; i -- ) {
while (!stk.empty() && heights[stk.peek()] >= heights[i]) stk.pop();
if (stk.empty()) r[i] = heights.length;
else r[i] = stk.peek();
stk.push(i);
}
int res = 0;
for (int i = 0; i < heights.length; i ++ ) {
res = Math.max(res, heights[i] * (r[i] - 1 - l[i] - 1 + 1));
}
return res;
}
}
7. 第85题 最大矩形
给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。
如果暴力写法的话就是枚举左上角和右下角,然后再遍历中间是否全是1
,然后取max
,很明显复杂度O(n^6)
,不可能过的。
直接想到前缀和,用O(1)
的复杂度解决掉遍历1
的过程,这样复杂度就降低到了O(n^4)
,emmm,还是很高,但是我赌leetcode
大水题不会卡我,再加上这是java
啊,时间比c++
宽裕,所以直接莽一发。
class Solution {
public int maximalRectangle(char[][] matrix) {
if (matrix.length == 0 || matrix[0].length == 0) return 0;
int[][] f = new int[matrix.length + 5][matrix[0].length + 5];
for (int i = 1; i <= matrix.length; i ++ ) {
for (int j = 1; j <= matrix[0].length; j ++ ) {
int t = 0;
if (matrix[i - 1][j - 1] == '1') t = 1;
f[i][j] = f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + t;
}
}
int res = 0;
for (int i1 = 1; i1 <= matrix.length; i1 ++ ) {
for (int j1 = 1; j1 <= matrix[0].length; j1 ++ ) {
for (int i2 = i1; i2 <= matrix.length; i2 ++ ) {
for (int j2 = j1; j2 <= matrix[0].length; j2 ++ ) {
int num = f[i2][j2] - f[i1 - 1][j2] - f[i2][j1 - 1] + f[i1 - 1][j1 - 1];
// System.out.println(num + " " + (i2 - i1 + 1) * (j2 - j1 + 1));
if (num == (i2 - i1 + 1) * (j2 - j1 + 1)) {
res = Math.max(res, num);
}
}
}
}
}
return res;
}
}
好家伙,真不给面子,直接TLE
了,但是很明显这是正确的,我直接看题解怎么做。
这也能和上一题产生练习??就离谱
正确思路:
通过枚举下边界,将上面的连续的一看成上一题的柱子,这样问题就转换为了上一题的矩阵,我直呼内行。。。那上一题的代码肯定是抄过来
class Solution {
public int largestRectangleArea(int[] heights) {
int n = heights.length;
int[] l = new int[n];
int[] r = new int[n];
Stack<Integer> stk = new Stack();
for (int i = 0; i < heights.length; i ++ ) {
// 把之前扫描过的且后面用不上的元素全部弹出去
while (!stk.empty() && heights[stk.peek()] >= heights[i]) stk.pop();
// 没有元素了说明前面没有比当前元素小的
if (stk.empty()) l[i] = -1;
else l[i] = stk.peek();
// 放入当前的元素索引
stk.push(i);
}
stk.clear();
for (int i = heights.length - 1; i >= 0; i -- ) {
while (!stk.empty() && heights[stk.peek()] >= heights[i]) stk.pop();
if (stk.empty()) r[i] = heights.length;
else r[i] = stk.peek();
stk.push(i);
}
int res = 0;
for (int i = 0; i < heights.length; i ++ ) {
res = Math.max(res, heights[i] * (r[i] - 1 - l[i] - 1 + 1));
}
return res;
}
public int maximalRectangle(char[][] matrix) {
if (matrix.length == 0 || matrix[0].length == 0) return 0;
int[] f = new int[matrix[0].length];
int res = 0;
for (int i = 0; i < matrix.length; i ++ ) {
for (int j = 0; j < matrix[0].length; j ++ ) {
if (i == 0 && matrix[i][j] == '1') f[j] = 1;
else if (i > 0 && matrix[i][j] == '1') f[j] ++ ;
else f[j] = 0;
}
res = Math.max(res, largestRectangleArea(f));
}
return res;
}
}
8. 第86题 分隔链表
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
你应当 保留 两个分区中每个节点的初始相对位置。
终于遇到一个简单题了,这居然标了个中等,我直接疑惑。。
开两个链表头,然后遍历原链表,给指向就行了
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode partition(ListNode head, int x) {
ListNode l1 = new ListNode();
ListNode l2 = new ListNode();
ListNode p1 = l1, p2 = l2;
ListNode p = head;
while (p != null) {
if (p.val < x) p1 = p1.next = p;
else p2 = p2.next = p;
p = p.next;
}
p1.next = l2.next;
p2.next = null;
return l1.next;
}
}
9. 第87题 扰乱字符串
给定一系列变换规则,问两个字符串,是否能通过这种规则从一个变成另一个
卧槽,咋全是难题呢,裂了啊今天
思路就是递归,枚举左右分割点,因为左右子树至少分得一个字母,所以无脑递归下去。
成立的条件是什么?
第一个字符串分割点左面的经过变换和第二个字符串同样位置可以一致
且 第一个字符串分割点右侧经过变换和第二个字符串同样的位置一致
这样就是当前字符串不经过旋转,让子串去旋转可以得到答案。
或者
第一个字符串分割点左面的经过变换和第二个字符串右侧对应长度可以一致
且 第一个字符串分割点右侧经过变换和第二个字符串左侧长度一致
这样是当前左右子串内部旋转了,自己也旋转了,得到答案。
class Solution {
boolean mysort(String a, String b) {
char[] ar = a.toCharArray();
Arrays.sort(ar);
String sorteda = String.valueOf(ar);
char[] br = b.toCharArray();
Arrays.sort(br);
String sortedb = String.valueOf(br);
return sorteda.equals(sortedb);
}
public boolean isScramble(String s1, String s2) {
if (s1.equals(s2)) return true;
String ss1 = new String(s1);
String ss2 = new String(s2);
if (!mysort(ss1, ss2)) return false; // 这里一定要剪枝,否则超时
for (int i = 1; i <= s1.length() - 1; i ++ ) {
if (isScramble(s1.substring(0, i), s2.substring(0, i))
&& isScramble(s1.substring(i), s2.substring(i))) return true;
if (isScramble(s1.substring(0, i), s2.substring(s1.length() - i))
&& isScramble(s1.substring(i), s2.substring(0, s2.length() - i))) return true;
}
return false;
}
}
10. 第88题 合并两个有序数组
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。你可以假设 nums1 的空间大小等于 m + n,这样它就有足够的空间保存来自 nums2 的元素
模拟即可,这也是二路归并排序的核心,唯一需要注意到的点就是从后往前遍历,防止把nums1的原数覆盖掉。当然可以不用nums1数组,最后赋值回去就可以了。
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int len = m + n - 1;
m -- ;
n -- ;
while (m >= 0 && n >= 0) {
if (nums1[m] > nums2[n]) nums1[len -- ] = nums1[m -- ];
else nums1[len -- ] = nums2[n -- ];
}
while (n >= 0) nums1[len -- ] = nums2[n -- ];
}
}
11. 第89题 格雷编码
格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个位数的差异。
给定一个代表编码总位数的非负整数 n,打印其格雷编码序列。即使有多个不同答案,你也只需要返回其中一种。
格雷编码序列必须以 0 开头。
可以看成找规律题,两个数字的二进制只有一位差异,前面已经形成的肯定是只差一位,我们把它复制过来,然后倒序,这样除了相接的地方就都是符合要求的,想接的一部分是怎么样的呢?因为是复制且倒序,那么这两个数字是一样的,让复制的那一个+1
即可,就只有一位不同了,这样其余复制过来的可能又不同了,怎么办?跟着它加就可以了。
class Solution {
public List<Integer> grayCode(int n) {
List<Integer> res = new ArrayList<>();
res.add(0);
while (n -- != 0) {
for (int i = res.size() - 1; i >= 0; i -- ) {
int u = res.get(i);
res.set(i, u * 2);
res.add(u * 2 + 1);
}
}
return res;
}
}