LeetCode 824. 山羊拉丁文 / 396. 旋转函数 / 587. 安装栅栏(不会,经典凸包问题,学)

95 阅读8分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

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)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。