第二十八天
1001 网格照明
在大小为 n x n
的网格 grid
上,每个单元格都有一盏灯,最初灯都处于 关闭 状态。
给你一个由灯的位置组成的二维数组 lamps
,其中 lamps[i] = [rowi, coli]
表示打开 位于 grid[rowi][coli]
的灯。即便同一盏灯可能在 lamps
中多次列出,不会影响这盏灯处于 打开 状态。
当一盏灯处于打开状态,它将会照亮 自身所在单元格 以及同一 行 、同一 列 和两条对角线 上的 所有其他单元格 。
另给你一个二维数组 queries
,其中 queries[j] = [rowj, colj]
。对于第 j
个查询,如果单元格 [rowj, colj]
是被照亮的,则查询结果为 1
,否则为 0
。在第 j
次查询之后 [按照查询的顺序] ,关闭 位于单元格 grid[rowj][colj]
上及相邻8 个方向上(与单元格 grid[rowi][coli]
共享角或边)的任何灯。
返回一个整数数组 ans
作为答案, ans[j]
应等于第 j
次查询 queries[j]
的结果,1
表示照亮,0
表示未照亮。
方法 线映射
建立四个映射表,分别用于记录x
轴方向上点亮的灯的数量,y
轴方向上点亮的灯的数量,正对角线上点亮的灯的数量,以及反对角线上点亮的灯的数量。由于同一盏灯可能会被多次点亮(即在lambs
数组中存在重复的元素)所以我们还需要一个哈希表用来记录所有出现过的灯的位置。
开始时,更新好所有的映射表和哈希表,每一次询问,只需要访问一次四个映射表中对应元素的数量是否大于1
即可,否则返回0
,询问完成之后,如果当前询问的点的九宫格内,存在主动点亮的光源,那我们就将这个光源熄灭,从哈希表中删除,同时要更新四个映射表即可。
关于正对角线和反对角线的映射,容易知道,所有在同一条正对角线上的点,它们之间的坐标(x1,y1)(x2,y2)
满足下列关系
x1+y1=x2+y2
x_1+y_1=x_2+y_2
x1+y1=x2+y2
因此,对于正对角线上的点,我们可以通过x+y
来唯一标识一条对角线,同理我们可以用x-y
或者y-x
来唯一标识一条反对角线。
class Solution {
public int[] dx = {0, 0, 0, 1, -1, 1, 1, -1, -1};
public int[] dy = {0, 1, -1, 0, 0, 1, -1, 1, -1};
public int[] gridIllumination(int n, int[][] lamps, int[][] queries) {
int[] ans = new int[queries.length];
Map<Integer, Integer> xAxis = new HashMap<>();
Map<Integer, Integer> yAxis = new HashMap<>();
Map<Integer, Integer> xy = new HashMap<>();
Map<Integer, Integer> yx = new HashMap<>();
Set<Point> set = new HashSet<>();
Point tempPoint = new Point(-1, -1);
for (int[] lamp : lamps){
tempPoint.x = lamp[0];
tempPoint.y = lamp[1];
if (!set.contains(tempPoint)){
xAxis.put(lamp[0], xAxis.getOrDefault(lamp[0], 0) + 1);
yAxis.put(lamp[1], yAxis.getOrDefault(lamp[1], 0) + 1);
xy.put(lamp[0] + lamp[1], xy.getOrDefault(lamp[0] + lamp[1], 0) + 1);
yx.put(lamp[0] - lamp[1], yx.getOrDefault(lamp[0] - lamp[1], 0) + 1);
set.add(new Point(lamp[0], lamp[1]));
}
}
int index = 0;
for (int[] query : queries){
if (xAxis.getOrDefault(query[0], 0) > 0
|| yAxis.getOrDefault(query[1], 0) > 0
|| xy.getOrDefault(query[0] + query[1], 0) > 0
|| yx.getOrDefault(query[0] - query[1], 0) > 0){
ans[index] = 1;
}
for (int i = 0; i < 9; ++i){
if (query[0] + dx[i] >= 0 && query[0] + dx[i] < n
&& query[1] + dy[i] >= 0 && query[1] + dy[i] < n){
tempPoint.x = query[0] + dx[i];
tempPoint.y = query[1] + dy[i];
if (set.contains(tempPoint)){
set.remove(tempPoint);
xAxis.put(tempPoint.x, xAxis.get(tempPoint.x) - 1);
yAxis.put(tempPoint.y, yAxis.get(tempPoint.y) - 1);
xy.put(tempPoint.x + tempPoint.y, xy.get(tempPoint.x + tempPoint.y) - 1);
yx.put(tempPoint.x - tempPoint.y, yx.get(tempPoint.x - tempPoint.y) - 1);
}
}
}
index++;
}
return ans;
}
}
class Point{
int x;
int y;
public Point(int x, int y){
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object p){
Point cmp = (Point) p;
return cmp.x == x && cmp.y == y;
}
@Override
public int hashCode(){
return x | y;
}
}
5 最长回文子串
给你一个字符串 s
,找到 s
中最长的回文子串。
方法 动态规划
考虑回文串的定义,对于一个字符串,如果和其反转字符串相同,那么这个字符串就是一个回文串。
依据回文串的定义,容易知道:
如果字符x
和字符y
相同,并且字符串S
是一个回文字符串,那么xSy
也一定是一个回文字符串。
利用上述性质,我们定义isPalindrome[i][j]
表示从i
到j
位置的子串是否为回文字符串。容易得出以下状态转移方程:
isPalindrome[i][j]=isPalindrome[i+1][j−1],ifs(i)==s(j)
isPalindrome[i][j]=isPalindrome[i+1][j-1],if \quad s(i)==s(j)
isPalindrome[i][j]=isPalindrome[i+1][j−1],ifs(i)==s(j)
即如果i+1,j-1
位置上子串是一个回文字符串,并且i
和j
位置上的字符相同,那么从i
到j
位置的子串一定也是一个回文字符串。
初始条件如下:
isPalindrome[i][i]=trueisPalindrome[i][i+1]=trueifs(i)==s(i+1)
isPalindrome[i][i]=true\\
isPalindrome[i][i+1]=true \quad if \quad s(i)==s(i+1)
isPalindrome[i][i]=trueisPalindrome[i][i+1]=trueifs(i)==s(i+1)
我们以长度为循环变量,初始长度为2
,遍历所有长度即可。
class Solution {
public String longestPalindrome(String s) {
if (s.length() == 1) return s;
boolean[][] isPalindrome = new boolean[s.length()][s.length()];
for (int i = 0; i < s.length(); ++i) isPalindrome[i][i] = true;
int maxL = 1;
for (int length = 2; length <= s.length(); ++length){
for (int start = 0; start <= s.length() - length; ++start){
int end = start + length - 1;
if (start + 1 < s.length()){
if (s.charAt(start) == s.charAt(end)) {
isPalindrome[start][end] = end - start + 1 == 2 ? true : isPalindrome[start + 1][end - 1];
}
if (isPalindrome[start][end]) maxL = Math.max(maxL, length);
}
}
}
for (int i = 0; i <= s.length() - maxL; ++i){
if (isPalindrome[i][i + maxL - 1]) return s.substring(i, i + maxL);
}
return s.substring(0, 1);
}
}
413 等差数列划分
如果一个数列 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该数列为等差数列。
- 例如,
[1,3,5,7,9]
、[7,7,7,7]
和[3,-1,-5,-9]
都是等差数列。
给你一个整数数组 nums
,返回数组 nums
中所有为等差数组的 子数组 个数。
子数组 是数组中的一个连续序列。
方法 贪心
依据等差数列和子数组的特性,我们只需要依次求出公差d
,然后从头往后一直找到第一个使得公差不为d
的位置end
,此时我们更新答案,直到真个数列被遍历完成即可。
class Solution {
public int numberOfArithmeticSlices(int[] nums) {
if (nums.length < 3) return 0;
int ans = 0, start = 0, end = 0;
while (start < nums.length - 2){
int d = nums[start + 1] - nums[start];
end = start + 1;
while (end < nums.length - 1) {
if (d == nums[end + 1] - nums[end]){
end++;
}
else break;
}
if (end - start + 1 >= 3){
int n = end - start -1;
ans += (1 + n) * n / 2;
}
start = end;
}
return ans;
}
}