LeetCode 181周赛

刚完成周赛,趁着思路还热乎,来写一篇题解。
总的来说,这次的题目难度还好,难度集中体现在三四题上。第三题需要砸不少代码,第四题需要对前缀数组知识的了解。
咱们一道一道说:


1.按既定顺序创建目标数组

题目
首先,第一题。题意很清晰,题目中给定两个数组,遍历两个数组,按照 index 数组的下标插入 num 数组中的元素。
这个题想要考察的点是线性结构的插入元素。所以我们选用链表来完成,鉴于 Java 中自带链表,这道题就成了模板的使用。
代码:

class Solution {
    public int[] createTargetArray(int[] nums, int[] index) {
		LinkedList<Integer> list = new LinkedList();
		int n = nums.length;
		for(int i = 0 ;i < n;i++)
		{
			list.add(index[i], nums[i]);
		}
		int[] ans = new int[n];
		for(int i = 0;i < n;i++)
		{
			ans[i] = list.get(i);
		}
		return ans;
    }
}

2.四因数

题目
这个题目,题意也非常的清晰,找出数组中因数个数为四个的元素,并计算这些因数的和。

遍历数组很简单,分解因数并统计个数也很简单,但有一个问题在于它的数据范围:

1 <= nums.length <= 10^4
1 <= nums[i] <= 10^5

这个数据范围是不允许我们暴力的循环枚举因子分解每一个元素的,那样的复杂度是O(NM)已经是 10^9 了,基本上可以宣告TLE。
所以需要在枚举因子上面做一些优化:每次枚举到其平方根即可。因为不难证明,因数的分布一定在一个数的平方根的两侧。

最后需要注意这个数是否是个平方数,如果是平方数,可以断言这个数的因数个数为奇数个,进而不可能为四个,可以省去后面的统计。
代码:

class Solution {
    private List<Integer> split(int k)
	{
		
		LinkedList<Integer> list = new LinkedList();
		if((int)Math.sqrt(k) * (int)Math.sqrt(k) == k)
		{
			list.add((int)Math.sqrt(k)); 
			return list;
		}
		for(int i = 1;i < Math.sqrt(k);i++)
		{
			if(k % i == 0)
			{
				list.add(i);
				list.add(k / i);
			}
			if(list.size() > 4)
			{
				return list;
			}
		}
		
		return list;
	}
	public int sumFourDivisors(int[] nums) {
		LinkedList<Integer> list = new LinkedList();
		int ans = 0;
		for(int k:nums)
		{
			list = (LinkedList<Integer>) split(k);
			if(list.size() == 4)
			{
				for(Integer i : list)
				{
					ans += i;
				}
			}
		}
		return ans;
    }
}

3.检查网格中是否存在有效路径

题目
这个题很有意思,思路也不难,但就是不太好写。(也可能是我比较笨)
网格上的问题,往往可以抽象成图的问题,而判断连通性的问题,往往都伴随着 bfs 的使用,这个题就是个很好的例子。上一个类似的例子,可以参考周赛178的T4。(不了解图或BFS的小伙伴可以参考这里)

不过,对于这个题来说,其实没有必要建图,直接 bfs 就好。可是问题就出在这个题目的规则是在是不太友好,连通的条件是两个方块需要对上,而以往类似的题目只需要单方面可行即可移动。而且每个方块还有两个可移动方向。

在仔细分析六条道路发现其实状态也不多,如果我们写出了12两种道路的上下左右四种情况,那么其实3456这四种道路是12的四选二组合(你品,你细品)。于是,对于这道题,就可以实行面向复制粘贴编程的策略了(滑稽)。

最终步骤:

  • 循环遍历搜索队列
    – 查看当前位置是否为答案
    – 根据当前位置道路类型将可以走到的格子加入到搜索队列中

这个过程看起来很简单 写起来并不容易。在判断是否可以到达下一个格子的时候需要判断:

  • 是否有下一个格子(是否会超界)
  • 下一个格子是否走过(vis数组)
  • 下一个格子是否有朝向这个格子的路

也就是说,三个条件需要同时满足,每个类型还有两个方向,233.

话不多说,上代码:

class Solution {
    public boolean hasValidPath(int[][] grid) {
		
		int n = grid.length;
		int m = grid[0].length;
		int[][] queue = new int[n * m + 10][2];
		boolean[][] vis = new boolean[n][m];
		int l = 1,r = 0;
		
		queue[++r][0] = 0;
		queue[r][1] = 0;
		vis[0][0] = true;
		
		int i,j;
		while(l <= r)
		{
			i = queue[l][0];
			j = queue[r][1];
			l++;
			if(i == n - 1 && j == m - 1)
			{
				return true;
			}
			
			if(grid[i][j] == 1)
			{
				if(j > 0 && !vis[i][j - 1] && 
						(grid[i][j - 1] == 4 || grid[i][j - 1] == 6 || grid[i][j - 1] == 1))
				{
					queue[++r][0] = i;
					queue[r][1] = j - 1;
					vis[i][j - 1] = true;
				}
				if(j < m - 1 && !vis[i][j + 1] && 
						(grid[i][j + 1] == 3 || grid[i][j + 1] == 5 || grid[i][j + 1] == 1))
				{
					queue[++r][0] = i;
					queue[r][1] = j + 1;
					vis[i][j + 1] = true;
				}
			}
			if(grid[i][j] == 2)
			{
				if(i > 0 && !vis[i - 1][j] && 
						(grid[i - 1][j] == 3 || grid[i - 1][j] == 4 || grid[i - 1][j] == 2))
				{
					queue[++r][0] = i - 1;
					queue[r][1] = j;
					vis[i - 1][j] = true;
				}
				
				if(i < n - 1 && !vis[i + 1][j] && 
						(grid[i + 1][j] == 6 || grid[i + 1][j] == 5 || grid[i + 1][j] == 2))
				{
					queue[++r][0] = i + 1;
					queue[r][1] = j;
					vis[i + 1][j] = true;
				}
			}
			if(grid[i][j] == 3)
			{
				if(j > 0 && !vis[i][j - 1] && 
						(grid[i][j - 1] == 4 || grid[i][j - 1] == 6 || grid[i][j - 1] == 1))
				{
					queue[++r][0] = i;
					queue[r][1] = j - 1;
					vis[i][j - 1] = true;
				}
				
				if(i < n - 1 && !vis[i + 1][j] && 
						(grid[i + 1][j] == 6 || grid[i + 1][j] == 5 || grid[i + 1][j] == 2))
				{
					queue[++r][0] = i + 1;
					queue[r][1] = j;
					vis[i + 1][j] = true;
				}
			}
			if(grid[i][j] == 4)
			{
				if(j < m - 1 && !vis[i][j + 1] && 
						(grid[i][j + 1] == 3 || grid[i][j + 1] == 5 || grid[i][j + 1] == 1))
				{
					queue[++r][0] = i;
					queue[r][1] = j + 1;
					vis[i][j + 1] = true;
				}
				
				if(i < n - 1 && !vis[i + 1][j] && 
						(grid[i + 1][j] == 6 || grid[i + 1][j] == 5 || grid[i + 1][j] == 2))
				{
					queue[++r][0] = i + 1;
					queue[r][1] = j;
					vis[i + 1][j] = true;
				}
			}
			if(grid[i][j] == 5)
			{
				if(j > 0 && !vis[i][j - 1] && 
						(grid[i][j - 1] == 4 || grid[i][j - 1] == 6 || grid[i][j - 1] == 1))
				{
					queue[++r][0] = i;
					queue[r][1] = j - 1;
					vis[i][j - 1] = true;
				}
				if(i > 0 && !vis[i - 1][j] && 
						(grid[i - 1][j] == 3 || grid[i - 1][j] == 4 || grid[i - 1][j] == 2))
				{
					queue[++r][0] = i - 1;
					queue[r][1] = j;
					vis[i - 1][j] = true;
				}
			}
			if(grid[i][j] == 6)
			{
				if(j < m - 1 && !vis[i][j + 1] && 
						(grid[i][j + 1] == 3 || grid[i][j + 1] == 5 || grid[i][j + 1] == 1))
				{
					queue[++r][0] = i;
					queue[r][1] = j + 1;
					vis[i][j + 1] = true;
				}
				if(i > 0 && !vis[i - 1][j] && 
						(grid[i - 1][j] == 3 || grid[i - 1][j] == 4 || grid[i - 1][j] == 2))
				{
					queue[++r][0] = i - 1;
					queue[r][1] = j;
					vis[i - 1][j] = true;
				}
			}
		}
		return false;
    }
}

4.最长快乐前缀

题目
最后一道题,其实没什么可说的。从前缀与后缀相同这一点可以大致判断这是一道前缀函数的模板题,而且数据规模貌似限制了绝大多数的暴力算法。
答案就是前缀数组最后一位的长度,返回subString即可。
虽然这个题抛开暴力算法不太好考虑,但是前缀函数其实写出来很短,而短小代码解决复杂问题的过程也是不好理解和得出的,所以这道题的难度就在于此。有关前缀函数和KMP算法这个就不展开了,感兴趣的小伙伴可以看这里
代码:

class Solution {
   public int[] getF(String str)
	{
		char[] ch = str.toCharArray();
		int[] f = new int[str.length()];
		for(int i = 1,j = 0;i < str.length();i++)
		{
			while(j > 0 && ch[j] != ch[i])
			{
				j = f[j - 1];
			}
			if(ch[j] == ch[i])
			{
				j++;
			}
			f[i] = j;
		}
		return f;
	}
	public String longestPrefix(String s) {
		int ans = getF(s)[s.length() - 1];
		return s.substring(0,ans);
    }
}

你可能感兴趣的:(LeetCode周赛,java,算法,数据结构,leetcode,链表)