地下城游戏(HARD)
- 正向动态规划:一开始想的是使用 d p [ i ] [ j ] dp[i][j] dp[i][j]表示到达(i,j)点,从(0,0)所可能的最小初始点数,但并不具有最优子结构!还考虑同时保存直到(i,j)点该种走法的剩余点数,也受到最优子结构的限制。
- 当正向不起作用,考虑反向DP。考虑从(i,j)开始到最右下角的子矩阵,可以求出从(i,j)到最右下角的最小初始点数。
- 从(i,j)只可能向右或向下,所以如果右边和下边的点到最右下方的最小初始点数求出来,就有:
D P [ i ] [ j ] = m a x ( 1 , m i n ( D P [ i ] [ j − 1 ] , D P [ i − 1 ] [ j ] ) − n u m [ i ] [ j ] ) DP[i][j] = max(1,min(DP[i][j-1],DP[i-1][j])-num[i][j] ) DP[i][j]=max(1,min(DP[i][j−1],DP[i−1][j])−num[i][j])
class Solution {
public int calculateMinimumHP(int[][] dungeon) {
int[] dp = new int[dungeon[0].length];
dp[dp.length - 1] = Math.max(1,1-dungeon[dungeon.length - 1][dp.length - 1]);
for(int j = dp.length - 2;j>=0;j--)
dp[j] = Math.max(dp[j+1] - dungeon[dungeon.length - 1][j],1);
int[] predp = new int[dp.length];
System.arraycopy(dp,0,predp,0,dp.length);
for(int i = dungeon.length - 2;i>=0;i--)
{
for(int j = dp.length - 1;j>=0;j--)
{
if(j == dp.length - 1)
dp[j] = Math.max(dp[j] - dungeon[i][j],1);
else
{
dp[j] = Math.max(1,Math.min(dp[j+1],predp[j]) - dungeon[i][j]);
}
}
System.arraycopy(dp,0,predp,0,dp.length);
}
return dp[0];
}
}
翻转字符串中的单词(MEDIUM)
- 空间 O ( 1 ) O(1) O(1)做法(想不到):去翻转整个字符串,再翻转每个单词(在去除所有的多余空格之后),虽然JAVA本身无法对字符串进行原地修改
class Solution {
public void reverse(char[] a,int i , int j)
{
while(i<=j)
{
char temp = a[i];
a[i] = a[j];
a[j] = temp;
i++;
j--;
}
}
public String reverseWords(String s) {
s = s.strip(); //移除开头和末尾的空格
s = s.replaceAll(" +"," "); //单个空格
char[] a = s.toCharArray();
int i = 0;
int j = s.length() - 1;
//反转整个字符串
reverse(a,i,j);
//反转每个单词
i = 0;
j = i;
while(i<a.length && j < a.length)
{
while(j<s.length() && a[j]!=' ')
{
j++;
}
reverse(a,i,j-1);
i = j + 1;
j = i;
}
StringBuilder ans = new StringBuilder();
for(i = 0;i<a.length;i++)
{
ans.append(a[i]);
}
return ans.toString();
}
}
最大间距(HARD)(基数排序)
重新写下原来学过的数据结构中基数排序的代码
- 设置10个队列,分别装载当前位为0-9的数字。从个位开始,遍历Num数组:
- 将当前数字加入表示当前数字当前位的队列
- 遍历完成一位后,从0表示的队列开始出队,出队元素从nums的第一个元素开始放,直到所有队列为空。
- 下面的实现比较巧妙:
- cnt用于记录buf当中哪一下标用于放置当前位的最后一个数字
- 注意重新入队时的顺序
class Solution {
public int maximumGap(int[] nums) {
if(nums.length < 2)
return 0;
long e = 1;
int max_val = Arrays.stream(nums).max().getAsInt();
while(e <= max_val)
{
int[] cnt = new int[10]; //统计每个digit
int[] buf = new int[nums.length];
for(int i = 0;i<nums.length;i++)
{
cnt[(nums[i] / (int)e)%10]++;
}
for(int i = 1;i<10;i++)
{
cnt[i] = cnt[i] + cnt[i-1]; //应该放置的位置的末尾
}
for(int i = nums.length-1;i>=0;i--)
{
buf[cnt[(nums[i] / (int)e)%10] - 1] = nums[i];
cnt[(nums[i] / (int)e)%10]--;
}
System.arraycopy(buf,0,nums,0,buf.length);
e*=10;
}
int ans = Integer.MIN_VALUE;
for(int i = 0;i<nums.length - 1;i++)
{
ans = Math.max(ans,nums[i+1] - nums[i]);
}
return ans;
}
}
周赛T4-字符串分组
超时的思路
- 字符串编码异或为0,或者长度相同且编码异或后仅有2个1,或者编码异或为2的次幂(即仅有一个1).先计算出这些信息,再使用并查集分类。
/**
* 用于实现并查集中每个结点
*/
class UNION_FIND_NODE<E>
{
public E data;
public UNION_FIND_NODE<E> p;
public int rank;
public UNION_FIND_NODE(E data)
{
this.data = data;
this.p = this;
this.rank = 0;
}
}
class UNION_FIND<E>
{
public void MAKE_SET(UNION_FIND_NODE<E> x)
{
x.p = x;
x.rank = 0;
}
public void UNION(UNION_FIND_NODE<E> x,UNION_FIND_NODE<E> y)
{
LINK(FIND_SET(x),FIND_SET(y));
}
public UNION_FIND_NODE<E> FIND_SET(UNION_FIND_NODE<E> x)
{
if(x!=x.p)
x.p = FIND_SET(x.p);
return x.p;
}
private void LINK(UNION_FIND_NODE<E> x,UNION_FIND_NODE<E> y)
{
if(x.rank > y.rank)
{
y.p = x;
}
else
{
x.p = y;
if(x.rank == y.rank)
y.rank++;
}
}
}
class Solution {
public int[] groupStrings(String[] words) {
UNION_FIND_NODE<Integer>[] a = new UNION_FIND_NODE[words.length];
UNION_FIND<Integer> e = new UNION_FIND<>();
for(int i = 0;i<words.length;i++)
{
a[i] = new UNION_FIND_NODE<Integer>(i);
e.MAKE_SET(a[i]);
}
int[] c = new int[26];
int count = 0;
for(int i = 1;i<=(1<<25);i = i<<1)
c[count++] = i;
Set<Integer> d = new HashSet<>();
for(int i = 0;i<c.length - 1;i++)
for(int j = i+1;j<c.length;j++)
d.add(c[i] + c[j]);
Set<Integer> h = new HashSet<>();
for(int i : c)
h.add(i);
int[] f = new int[words.length]; //记录编码
for(int i = 0;i<words.length;i++)
{
for(int j = 0;j<words[i].length();j++)
{
f[i] += (1 << (words[i].charAt(j) - 'a'));
}
}
for(int i = 0;i<words.length - 1;i++)
for(int j = i+1;j<words.length;j++)
{
if(((f[i] ^ f[j])== 0) || (words[i].length() == words[j].length() && d.contains((f[i] ^ f[j]))) || h.contains(f[i] ^ f[j]))
e.UNION(a[i],a[j]);
}
Map<Integer,Integer> k = new HashMap<>();
for(int i = 0;i<words.length;i++)
{
UNION_FIND_NODE<Integer> o = e.FIND_SET(a[i]);
k.put(o.data,k.getOrDefault(o.data,0) + 1);
}
int[] ans = new int[2];
ans[0] = k.keySet().size();
for(int i : k.keySet())
{
ans[1] = Math.max(ans[1],k.get(i));
}
return ans;
}
}
- 超时原因分析:建立并查集时 O ( n 2 ) O(n^2) O(n2)遍历了字符串数组,有许多不必要的重复遍历!
- 改进:对每个字符串是否需要遍历其后的所有字符串?不需要!仅需考察所有可能的变化结果! O ( 2 6 2 n ) O(26^2 n) O(262n)。
- 注意重复字符串,先将其合并即可。
/**
* 用于实现并查集中每个结点
*/
class UNION_FIND_NODE<E>
{
public E data;
public UNION_FIND_NODE<E> p;
public int rank;
public UNION_FIND_NODE(E data)
{
this.data = data;
this.p = this;
this.rank = 0;
}
}
class UNION_FIND<E>
{
public void MAKE_SET(UNION_FIND_NODE<E> x)
{
x.p = x;
x.rank = 0;
}
public void UNION(UNION_FIND_NODE<E> x,UNION_FIND_NODE<E> y)
{
LINK(FIND_SET(x),FIND_SET(y));
}
public UNION_FIND_NODE<E> FIND_SET(UNION_FIND_NODE<E> x)
{
if(x!=x.p)
x.p = FIND_SET(x.p);
return x.p;
}
private void LINK(UNION_FIND_NODE<E> x,UNION_FIND_NODE<E> y)
{
if(x.rank > y.rank)
{
y.p = x;
}
else
{
x.p = y;
if(x.rank == y.rank)
y.rank++;
}
}
}
class Solution {
public int[] groupStrings(String[] words) {
UNION_FIND_NODE<Integer>[] a = new UNION_FIND_NODE[words.length];
UNION_FIND<Integer> e = new UNION_FIND<>();
Map<Integer,List<Integer>> h = new HashMap<>();
int[] f = new int[words.length];
for(int i = 0;i<words.length;i++)
{
for(int j = 0;j<words[i].length();j++)
{
f[i] += (1 << (words[i].charAt(j) - 'a'));
}
}
for(int i = 0;i<words.length;i++)
{
List<Integer> tmp = h.getOrDefault(f[i],new ArrayList<Integer>());
tmp.add(i);
h.put(f[i],tmp);
a[i] = new UNION_FIND_NODE<Integer>(i);
e.MAKE_SET(a[i]);
}
for(Integer l : h.keySet())
{
if(h.get(l).size() > 1)
{
for(int i = 1;i<h.get(l).size();i++)
e.UNION(a[h.get(l).get(i)],a[h.get(l).get(i-1)]); //合并重复的字符串
}
}
for(int i = 0;i<words.length;i++)
{
if(h.containsKey(f[i]))
{
h.remove(f[i]);
for(int j = 0;j<=25;j++)
{
//添加
if(((f[i] >> j) & 1) == 0 && h.containsKey(((1 << j) | f[i])))
e.UNION(a[i],a[h.get(((1 << j) | f[i])).get(0)]);
//删除
if(((f[i] >> j) & 1) == 1 && h.containsKey((~(1 << j) & f[i])))
e.UNION(a[i],a[h.get((~(1 << j) & f[i])).get(0)]);
//替换
if(((f[i] >> j)&1) == 1)
{
for(int k = 0;k<=25;k++)
{
if(k!=j && (((f[i] >> k) & 1) == 0))
{
int tmp = (~(1 << j) & f[i]) | (1 << k);
if(h.containsKey(tmp))
e.UNION(a[i],a[h.get(tmp).get(0)]);
}
}
}
}
}
}
Map<Integer,Integer> k = new HashMap<>();
for(int i = 0;i<words.length;i++)
{
UNION_FIND_NODE<Integer> o = e.FIND_SET(a[i]);
k.put(o.data,k.getOrDefault(o.data,0) + 1);
}
int[] ans = new int[2];
ans[0] = k.keySet().size();
for(int i : k.keySet())
{
ans[1] = Math.max(ans[1],k.get(i));
}
return ans;
}
}
T3(滚动哈希+反向滑动窗口)
虽然暴力可以过,但是反向滑动窗口
O
(
n
)
O(n)
O(n)。充分利用两数乘积取余的结论。