本文已参与「新人创作礼」活动,一起开启掘金创作之路。
824. 山羊拉丁文
2022.4.21 每日一题
题目描述
给你一个由若干单词组成的句子 sentence ,单词间由空格分隔。每个单词仅由大写和小写英文字母组成。
请你将句子转换为 “山羊拉丁文(Goat Latin)”(一种类似于 猪拉丁文 - Pig Latin 的虚构语言)。山羊拉丁文的规则如下:
- 如果单词以元音开头('a', 'e', 'i', 'o', 'u'),在单词后添加"ma"。 例如,单词 "apple" 变为 "applema" 。
- 如果单词以辅音字母开头(即,非元音字母),移除第一个字符并将它放到末尾,之后再添加"ma"。 例如,单词 "goat" 变为 "oatgma" 。
- 根据单词在句子中的索引,在单词最后添加与索引相同数量的字母'a',索引从 1 开始。 例如,在第一个单词后添加 "a" ,在第二个单词后添加 "aa" ,以此类推。
返回将 sentence 转换为山羊拉丁文后的句子。
示例 1:
输入:sentence = "I speak Goat Latin" 输出:"Imaa peaksmaaa oatGmaaaa atinLmaaaaa"
示例 2:
输入:sentence = "The quick brown fox jumped over the lazy dog" 输出:"heTmaa uickqmaaa rownbmaaaa oxfmaaaaa umpedjmaaaaaa overmaaaaaaa hetmaaaaaaaa azylmaaaaaaaaa ogdmaaaaaaaaaa"
提示:
1 <= sentence.length <= 150 sentence 由英文字母和空格组成 sentence 不含前导或尾随空格 sentence 中的所有单词由单个空格分隔
来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/go… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
判断然后模拟就行了
class Solution {
public String toGoatLatin(String sentence) {
Set<Character> set = new HashSet<>();
set.add('a');
set.add('e');
set.add('i');
set.add('o');
set.add('u');
set.add('A');
set.add('E');
set.add('I');
set.add('O');
set.add('U');
String[] ss = sentence.split(" ");
for(int i = 0; i < ss.length; i++){
String s = ss[i];
if(set.contains(s.charAt(0))){
s = s + "ma";
for(int j = 1; j <= i + 1; j++)
s = s + "a";
ss[i] = s;
}else{
s = s.substring(1, s.length()) + s.charAt(0) + "ma";
for(int j = 1; j <= i + 1; j++)
s = s + "a";
ss[i] = s;
}
}
String res = "";
for(String s : ss)
res = res + s + " ";
return res.substring(0, res.length() - 1);
}
}
class Solution:
def toGoatLatin(self, sentence: str) -> str:
yuan = {'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'};
ss = sentence.split()
res = ''
for idx, word in enumerate(ss):
if word[0] not in yuan:
word = word[1:] + word[0]
word += 'ma' + (idx + 1) * 'a'
res += word + ' '
return res[0:-1]
396. 旋转函数
2022.4.22 每日一题
题目描述
给定一个长度为 n 的整数数组 nums 。
假设 arrk 是数组 nums 顺时针旋转 k 个位置后的数组,我们定义 nums 的 旋转函数 F 为:
F(k) = 0 * arrk[0] + 1 * arrk[1] + ... + (n - 1) * arrk[n - 1] 返回 F(0), F(1), ..., F(n-1)中的最大值 。
生成的测试用例让答案符合 32 位 整数。
示例 1:
输入: nums = [4,3,2,6] 输出: 26 解释: F(0) = (0 * 4) + (1 * 3) + (2 * 2) + (3 * 6) = 0 + 3 + 4 + 18 = 25 F(1) = (0 * 6) + (1 * 4) + (2 * 3) + (3 * 2) = 0 + 4 + 6 + 6 = 16 F(2) = (0 * 2) + (1 * 6) + (2 * 4) + (3 * 3) = 0 + 6 + 8 + 9 = 23 F(3) = (0 * 3) + (1 * 2) + (2 * 6) + (3 * 4) = 0 + 2 + 12 + 12 = 26 所以 F(0), F(1), F(2), F(3) 中的最大值是 F(3) = 26 。
示例 2:
输入: nums = [100] 输出: 0
提示:
n == nums.length 1 <= n <= 10^5 -100 <= nums[i] <= 100
来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/ro… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
找规律
class Solution {
public int maxRotateFunction(int[] nums) {
//首先,因为数据范围很大,所以不能暴力
//那么自然而然就想到了能运用什么规律,然后再想相邻两个F之间有什么关系
//例如f(1) = f(0) + sum - nums[l-1] - (l-1)*nums[l - 1]
//总结一下 f(x) = f(x-1) + sum - l*nums[l - x]
int sum = 0;
for(int n : nums)
sum += n;
int l = nums.length;
int f0 = 0;
for(int i = 0; i < l; i++){
f0 += i * nums[i];
}
int max = f0;
for(int i = 1; i < l; i++){
f0 = f0 + sum - l * nums[l - i];
max = Math.max(max, f0);
}
return max;
}
}
class Solution:
def maxRotateFunction(self, nums: List[int]) -> int:
s = sum(nums)
f = sum(i * n for i, n in enumerate(nums))
res = f
l = len(nums)
for i in range(1, l):
f = f + s - l * nums[-i]
res = max(res, f)
return res
587. 安装栅栏
2022.4.23 每日一题
题目描述
在一个二维的花园中,有一些用 (x, y) 坐标表示的树。由于安装费用十分昂贵,你的任务是先用最短的绳子围起所有的树。只有当所有的树都被绳子包围时,花园才能围好栅栏。你需要找到正好位于栅栏边界上的树的坐标。
示例 1:
输入: [[1,1],[2,2],[2,0],[2,4],[3,3],[4,2]] 输出: [[1,1],[2,0],[4,2],[3,3],[2,4]] 解释:
示例 2:
输入: [[1,2],[2,2],[4,2]] 输出: [[1,2],[2,2],[4,2]] 解释:
即使树都在一条直线上,你也需要先用绳子包围它们。
注意:
所有的树应当被围在一起。你不能剪断绳子来包围树或者把树分成一组以上。 输入的整数在 0 到 100 之间。 花园至少有一棵树。 所有树的坐标都是不同的。 输入的点没有顺序。输出顺序也没有要求。
来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/er… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
真·不会做 想了半天,明白题的意思是找最外围的边界,但是怎么找不会 想到一个方法是先将最上下左右的点连接,形成一个图形,然后遍历其他点,如果点在图形内,那么不能添加,如果在图形外,可以添加 然后去百度了怎么判断一个点在图形内,找到一个做射线找几个交点的方法,想了一下,代码太复杂,而且怎么做线什么的都是问题,不可行
只能看题解了,具体看题解: leetcode-cn.com/problems/er…
这里说一下我的简单理解 首先第一个算法,Jarvis算法,就是先确定一个最左边的点,然后依次与其他所有点连线,判断哪条连线在做左边或者最右边 具体怎么判断呢,就是通过向量叉乘的方式,两个向量叉乘,得到的是一个向量。a,b两个向量叉乘的模 |a×b| = |a|*|b|*sin(向量a,b的夹角) (这个夹角和点乘的夹角有点不同,最大的区别就是点乘是0到180度,指的是两个向量之间的夹角,而叉乘的夹角是0到360度,是指一个向量转动到另一个向量的角度) 所以说通过这个叉乘,可以判断两个向量之间的夹角是否是大于180度的,这样就可以判断一条边是否在另一条边的左边 用这样的方法,从最左边的点开始,找最右边的边,也就是其他点都在这条边的左边 然后以新的起点出发,找新的边,保证其他点在这个边的左边,这样依次找到一个闭环,就是所要的凸包
这里有个问题,就是在所给的这个代码中,加入了visit数组,判断每个点是否被加入到res中,如果加入了,那么就不重复加入;但是这个有必要吗,或者说正确吗,如果存在这种情况的话,当找到一个已经遍历过的点q,那么再从q出发,遍历到的点还是在res中,就形成了循环,跳不出去了;当然,除非这个点就是出发点,只有这一种可能 所以这个算法要是行得通,那么必须保证加入的每一个点都不是重复点,也就是说,从任意一个新的点出发找到的另一个点形成的边都是全新的,这很显然是符合逻辑的,也就是说这个代码中,可以在判断r的时候,就判断是否在visit中出现过,但是这样就会对最后一个点造成影响,或者说加入visit的时候,判断是否是出发点就行(实际改写了一下,不太行,因为同一条线上的点会重复加入) 但是要明白这个道理,就是说不会出现循环的情况
class Solution {
public int[][] outerTrees(int[][] trees) {
int n = trees.length;
if (n < 4) {
return trees;
}
//找最左下角的点
int leftMost = 0;
for (int i = 0; i < n; i++) {
if (trees[i][0] < trees[leftMost][0] ||
(trees[i][0] == trees[leftMost][0] &&
trees[i][1] < trees[leftMost][1])) {
leftMost = i;
}
}
List<int[]> res = new ArrayList<int[]>();
boolean[] visit = new boolean[n];
//p最开始为最左下方的点
int p = leftMost;
do {
//与随便一个q相连
int q = (p + 1) % n;
//遍历其他所有点,如果pq与qr两条边的夹角大于180度,也就是这个叉集小于0,
//那么说明点r在pq的右边,那么就加q替换成r
//最后找到最右边的点,保证其他所有点都在pq的左边
for (int r = 0; r < n; r++) {
/* 如果 r 在 pq 的右侧,则 q = r */
if (cross(trees[p], trees[q], trees[r]) < 0) {
q = r;
}
}
/* 是否存在点 i, 使得 p 、q 、i 在同一条直线上 */
for (int i = 0; i < n; i++) {
if (visit[i] || i == p || i == q) {
continue;
}
//在用一条线上的点,要加入结果集
if (cross(trees[p], trees[q], trees[i]) == 0) {
res.add(trees[i]);
visit[i] = true;
}
}
//如果当前点没有遍历过,那么加入结果集
if (!visit[q]) {
res.add(trees[q]);
visit[q] = true;
}
//下一轮从q开始出发继续找点
p = q;
//直到围城一个图形,即开始和结尾的点相遇
} while (p != leftMost);
return res.toArray(new int[][]{});
}
public int cross(int[] p, int[] q, int[] r) {
return (q[0] - p[0]) * (r[1] - q[1]) - (q[1] - p[1]) * (r[0] - q[0]);
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/erect-the-fence/solution/an-zhuang-zha-lan-by-leetcode-solution-75s3/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution {
public int[][] outerTrees(int[][] trees) {
int n = trees.length;
if (n < 4) {
return trees;
}
int bottom = 0;
/* 找到 y 最小的点 bottom*/
for (int i = 0; i < n; i++) {
if (trees[i][1] < trees[bottom][1]) {
bottom = i;
}
}
swap(trees, bottom, 0);
/* 以 bottom 原点,按照极坐标的角度大小进行排序 */
Arrays.sort(trees, 1, n, (a, b) -> {
int diff = cross(trees[0], a, b);
if (diff == 0) {
return distance(trees[0], a) - distance(trees[0], b);
} else {
return -diff;
}
});
/* 对于凸包最后且在同一条直线的元素按照距离从大到小进行排序 */
int r = n - 1;
while (r >= 0 && cross(trees[0], trees[n - 1], trees[r]) == 0) {
r--;
}
for (int l = r + 1, h = n - 1; l < h; l++, h--) {
swap(trees, l, h);
}
Deque<Integer> stack = new ArrayDeque<Integer>();
stack.push(0);
stack.push(1);
for (int i = 2; i < n; i++) {
int top = stack.pop();
/* 如果当前元素与栈顶的两个元素构成的向量顺时针旋转,则弹出栈顶元素 */
while (!stack.isEmpty() && cross(trees[stack.peek()], trees[top], trees[i]) < 0) {
top = stack.pop();
}
stack.push(top);
stack.push(i);
}
int size = stack.size();
int[][] res = new int[size][2];
for (int i = 0; i < size; i++) {
res[i] = trees[stack.pop()];
}
return res;
}
public int cross(int[] p, int[] q, int[] r) {
return (q[0] - p[0]) * (r[1] - q[1]) - (q[1] - p[1]) * (r[0] - q[0]);
}
public int distance(int[] p, int[] q) {
return (p[0] - q[0]) * (p[0] - q[0]) + (p[1] - q[1]) * (p[1] - q[1]);
}
public void swap(int[][] trees, int i, int j) {
int temp0 = trees[i][0], temp1 = trees[i][1];
trees[i][0] = trees[j][0];
trees[i][1] = trees[j][1];
trees[j][0] = temp0;
trees[j][1] = temp1;
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/erect-the-fence/solution/an-zhuang-zha-lan-by-leetcode-solution-75s3/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
第二个算法:Graham 算法 理解了第一个,那么第二个就很好理解, 选最下面的点bottom,首先将所有点按照与bottom的夹角排序,夹角相同按照距离从小到大排序 但是需要注意的是,在排序的最后,也就是凸包的最后一条边,因为在最后一条边连接的时候,是从远到近相连的,所以需要将这个边上的点反向排序,这样可以保证形成凸包的过程中,是按照我们预想的顺序形成的(这里又很容易想到一个问题,就是最后这个共线的点一定是结果里面的点吗,应该是肯定的,因为刚开始选的点是最边上的点,而与这个点夹角最大的点,肯定在凸包的边上) 排好序后,用栈处理所有点边,判断栈顶的边和当前要加入边的夹角,如果大于180,那么就弹出栈顶,小于加入栈,最后栈中的点就是凸包中的点
class Solution {
public int[][] outerTrees(int[][] trees) {
int n = trees.length;
if (n < 4) {
return trees;
}
int bottom = 0;
/* 找到 y 最小的点 bottom*/
for (int i = 0; i < n; i++) {
if (trees[i][1] < trees[bottom][1]) {
bottom = i;
}
}
swap(trees, bottom, 0);
/* 以 bottom 原点,按照极坐标的角度大小进行排序 */
Arrays.sort(trees, 1, n, (a, b) -> {
int diff = cross(trees[0], a, b);
if (diff == 0) {
return distance(trees[0], a) - distance(trees[0], b);
} else {
return -diff;
}
});
/* 对于凸包最后且在同一条直线的元素按照距离从大到小进行排序 */
//如果有与最后一条边共线的点,那么将这些点反向排序
int r = n - 1;
while (r >= 0 && cross(trees[0], trees[n - 1], trees[r]) == 0) {
r--;
}
for (int l = r + 1, h = n - 1; l < h; l++, h--) {
swap(trees, l, h);
}
Deque<Integer> stack = new ArrayDeque<Integer>();
stack.push(0);
stack.push(1);
for (int i = 2; i < n; i++) {
int top = stack.pop();
/* 如果当前元素与栈顶的两个元素构成的向量顺时针旋转,则弹出栈顶元素 */
while (!stack.isEmpty() && cross(trees[stack.peek()], trees[top], trees[i]) < 0) {
top = stack.pop();
}
stack.push(top);
stack.push(i);
}
int size = stack.size();
int[][] res = new int[size][2];
for (int i = 0; i < size; i++) {
res[i] = trees[stack.pop()];
}
return res;
}
public int cross(int[] p, int[] q, int[] r) {
return (q[0] - p[0]) * (r[1] - q[1]) - (q[1] - p[1]) * (r[0] - q[0]);
}
public int distance(int[] p, int[] q) {
return (p[0] - q[0]) * (p[0] - q[0]) + (p[1] - q[1]) * (p[1] - q[1]);
}
public void swap(int[][] trees, int i, int j) {
int temp0 = trees[i][0], temp1 = trees[i][1];
trees[i][0] = trees[j][0];
trees[i][1] = trees[j][1];
trees[j][0] = temp0;
trees[j][1] = temp1;
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/erect-the-fence/solution/an-zhuang-zha-lan-by-leetcode-solution-75s3/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
方法三:Andrew 算法 相比于第二个算法,将这个凸包分成了上下两个部分,然后依次处理 这里的排序直接按照下标x从小到大排序,如果x相同,那么按照y排序 从最左边的点出发,同样是判断夹角,小于180那么就加入栈中,这样遍历到最后,可以处理完下半部分;然后倒序处理,得到上半部分 这个方法不需要对共线的情况进行特殊的处理,因为已经在排序中按含了
这三个算法循序渐进,真的巧妙,看了官解所给的图,会有更加清晰的认知
class Solution {
public int[][] outerTrees(int[][] trees) {
int n = trees.length;
if (n < 4) {
return trees;
}
/* 按照 x 大小进行排序,如果 x 相同,则按照 y 的大小进行排序 */
Arrays.sort(trees, (a, b) -> {
if (a[0] == b[0]) {
return a[1] - b[1];
}
return a[0] - b[0];
});
List<Integer> hull = new ArrayList<Integer>();
boolean[] used = new boolean[n];
/* hull[0] 需要入栈两次,不进行标记 */
hull.add(0);
/* 求出凸包的下半部分 */
for (int i = 1; i < n; i++) {
while (hull.size() > 1 && cross(trees[hull.get(hull.size() - 2)], trees[hull.get(hull.size() - 1)], trees[i]) < 0) {
used[hull.get(hull.size() - 1)] = false;
hull.remove(hull.size() - 1);
}
used[i] = true;
hull.add(i);
}
int m = hull.size();
/* 求出凸包的上半部分 */
for (int i = n - 2; i >= 0; i--) {
if (!used[i]) {
//这里需要保证hull的大小永远是大于m的,不能把上半部分的点弹出去
while (hull.size() > m && cross(trees[hull.get(hull.size() - 2)], trees[hull.get(hull.size() - 1)], trees[i]) < 0) {
used[hull.get(hull.size() - 1)] = false;
hull.remove(hull.size() - 1);
}
used[i] = true;
hull.add(i);
}
}
/* hull[0] 同时参与凸包的上半部分检测,因此需去掉重复的 hull[0] */
hull.remove(hull.size() - 1);
int size = hull.size();
int[][] res = new int[size][2];
for (int i = 0; i < size; i++) {
res[i] = trees[hull.get(i)];
}
return res;
}
public int cross(int[] p, int[] q, int[] r) {
return (q[0] - p[0]) * (r[1] - q[1]) - (q[1] - p[1]) * (r[0] - q[0]);
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/erect-the-fence/solution/an-zhuang-zha-lan-by-leetcode-solution-75s3/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。