欢迎大家加QQ群:623375442,可以方便群里面交流。春节期间刷题,也是真爱了。幸好这周也不算很难。

100552. Find Valid Pair of Adjacent Digits in String

给你一个只包含数字的字符串 s 。如果 s 中两个 相邻 的数字满足以下条件,我们称它们是 合法的 :

  • 前面的数字 不等于 第二个数字。
  • 两个数字在 s 中出现的次数 恰好 分别等于这个数字本身。

请你从左到右遍历字符串 s ,并返回最先找到的 合法 相邻数字。如果这样的相邻数字不存在,请你返回一个空字符串。

测试样例:

输入:s = "2523533"

输出:"23"

解释:数字 '2' 出现 2 次,数字 '3' 出现 3 次。"23" 中每个数字在 s 中出现的次数都恰好分别等于数字本身。所以输出 "23" 。

解答:暴力运算。

class Solution {
    public String findValidPair(String s) {
        int[] count = new int[10];
        for (int i = 0; i < s.length(); ++i) {
            count[s.charAt(i) - '0'] += 1;
        }
        for (int i = 1; i < s.length(); ++i) {
            if (s.charAt(i - 1) != s.charAt(i) && count[s.charAt(i - 1) - '0'] == s.charAt(i - 1) - '0' && count[s.charAt(i) - '0'] == s.charAt(i) - '0') {
                return s.substring(i - 1, i + 1);
            }
        }
        return "";
    }
}

100558. Reschedule Meetings for Maximum Free Time I

给你一个整数 eventTime 表示一个活动的总时长,这个活动开始于 t = 0 ,结束于 t = eventTime 。

同时给你两个长度为 n 的整数数组 startTime 和 endTime 。它们表示这次活动中 n 个时间 没有重叠 的会议,其中第 i 个会议的时间为 [startTime[i], endTime[i]] 。

你可以重新安排 至多 k 个会议,安排的规则是将会议时间平移,且保持原来的 会议时长 ,你的目的是移动会议后 最大化 相邻两个会议之间的 最长 连续空余时间。

移动前后所有会议之间的 相对 顺序需要保持不变,而且会议时间也需要保持互不重叠。

请你返回重新安排会议以后,可以得到的 最大 空余时间。

注意,会议 不能 安排到整个活动的时间以外。

测试样例:

输入:eventTime = 5, k = 1, startTime = [1,3], endTime = [2,5]

输出:2

解释:将 [1, 2] 的会议安排到 [2, 3] ,得到空余时间 [0, 2] 。

解答:尽量把k个会议一起进行,这样可以空出最大的实践。

class Solution {
    public int maxFreeTime(int eventTime, int k, int[] startTime, int[] endTime) {
        int[] prefixCount = new int[startTime.length + 1];
        for (int i = 0; i < startTime.length; ++i) {
            prefixCount[i + 1] = prefixCount[i] + endTime[i] - startTime[i];
        }
        int best = 0;
        for (int i = 0; i + k <= startTime.length; ++i) {
            int left = i == 0 ? 0 : endTime[i - 1];
            int right = i + k < startTime.length ? startTime[i + k] : eventTime;
            // k个会议一起进行
            best = Math.max(best, right - left - prefixCount[i + k] + prefixCount[i]);
        }
        return best;
    }
}

100556. Reschedule Meetings for Maximum Free Time II

给你一个整数 eventTime 表示一个活动的总时长,这个活动开始于 t = 0 ,结束于 t = eventTime 。

同时给你两个长度为 n 的整数数组 startTime 和 endTime 。它们表示这次活动中 n 个时间 没有重叠 的会议,其中第 i 个会议的时间为 [startTime[i], endTime[i]] 。

你可以重新安排 至多 一个会议,安排的规则是将会议时间平移,且保持原来的 会议时长 ,你的目的是移动会议后 最大化 相邻两个会议之间的 最长 连续空余时间。

移动前后所有会议之间的 相对 顺序需要保持不变,而且会议时间也需要保持互不重叠。

请你返回重新安排会议以后,可以得到的 最大 空余时间。

注意,会议 不能 安排到整个活动的时间以外,且会议之间需要保持互不重叠。

注意:重新安排会议以后,会议之间的顺序可以发生改变。

测试样例:

输入:eventTime = 5, startTime = [1,3], endTime = [2,5]

输出:2

解释:将 [1, 2] 的会议安排到 [2, 3] ,得到空余时间 [0, 2] 。

解答:主要难点是,会议可能会被移去别的空档。利用topThree来记录最大的三个空档(每次最多删除2个空档,这个时候性能较好)。

class Solution {
    public int maxFreeTime(int eventTime, int[] startTime, int[] endTime) {
        int[] topThree = new int[3];
        int l = startTime.length - 1;
        updateTopThree(topThree, startTime[0]);
        updateTopThree(topThree, eventTime - endTime[l]);
        for (int i = 1; i <= l; ++i) {
            updateTopThree(topThree, startTime[i] - endTime[i - 1]);
        }
        int res = 0;
        int left = 0;
        for (int i = 0; i <= l; ++i) {
            int right = i == l ? eventTime : startTime[i + 1];
            res = Math.max(res, topAfterRemove(topThree, startTime[i] - left, right - endTime[i]) >= endTime[i] - startTime[i]
                    ? (right - left) : (right - left - endTime[i] + startTime[i]));
            left = endTime[i];
        }
        return res;
    }

    private void updateTopThree(int[] topThree, int val) {
        for (int i = 2; i >= 0; --i) {
            if (topThree[i] <= val) {
                for (int j = 0; j < i; ++j) {
                    topThree[j] = topThree[j + 1];
                }
                topThree[i] = val;
                break;
            }
        }
    }

    private int topAfterRemove(int[] topThree, int val1, int val2) {
        for (int i = 2; i >= 0; --i) {
            if (topThree[i] == val1) {
                val1 = -1;
            } else if (topThree[i] == val2) {
                val2 = -1;
            } else {
                return topThree[i];
            }
        }
        return -1;
    }
}

100524. Minimum Cost Good Caption

给你一个长度为 n 的字符串 caption 。如果字符串中 每一个 字符都位于连续出现 至少 3 次 的组中,那么我们称这个字符串是 好 标题。

比方说:

  • "aaabbb" 和 "aaaaccc" 都是 好 标题。
  • "aabbb" 和 "ccccd" 都 不是 好标题。

你可以对字符串执行以下操作 任意 次:

选择一个下标 i(其中 0 <= i < n )然后将该下标处的字符变为:

  • 该字符在字母表中 前 一个字母(前提是 caption[i] != 'a' )
  • 该字符在字母表中 后 一个字母(caption[i] != 'z' )

你的任务是用 最少 操作次数将 caption 变为 好 标题。如果存在 多种 好标题,请返回它们中 字典序最小 的一个。如果 无法 得到好标题,请你返回一个空字符串 "" 。

在字符串 a 和 b 中,如果两个字符串第一个不同的字符处,字符串 a 的字母比 b 的字母在字母表里出现的顺序更早,那么我们称字符串 a 的 字典序 比 b 小 。如果两个字符串前 min(a.length, b.length) 个字符都相同,那么较短的一个字符串字典序比另一个字符串小。

测试样例:

输入:caption = "cdcd"

输出:"cccc"

解释:无法用少于 2 个操作将字符串变为好标题。2 次操作得到好标题的方案包括:

  • "dddd" :将 caption[0] 和 caption[2] 变为它们后一个字符 'd' 。
  • "cccc" :将 caption[1] 和 caption[3] 变为它们前一个字符 'c' 。

由于 "cccc" 字典序比 "dddd" 小,所以返回 "cccc" 。

解答:典型的动态规划。规划计算当前字段开始,连续1,2,3这三种情况下,最小的支出。

class Solution {
    private static final int MAX = Integer.MAX_VALUE / 2;

    public String minCostGoodCaption(String caption) {
        if (caption.length() <= 2) return "";
        int[][][] dp = new int[caption.length()][26][3];
        int min = MAX;
        for (int i = caption.length() - 1; i  >= 0; --i) {
            initialArr(dp[i]);
            if (i == caption.length() - 1) {
                for (int j = 0; j < 26; ++j) {
                    dp[i][j][0] = Math.abs(caption.charAt(i) - 'a' - j);
                }
            } else {
                int nextMin = MAX;
                for (int j = 0; j < 26; ++j) {
                    // 连续1,2,3这三种情况下,最小的支出。
                    int diff = Math.abs(caption.charAt(i) - 'a' - j);
                    dp[i][j][0] = Math.min(min + diff, MAX);
                    dp[i][j][1] = Math.min(Math.min(dp[i + 1][j][0], Math.min(dp[i + 1][j][1], dp[i + 1][j][2])) + diff, MAX);
                    dp[i][j][2] = Math.min(Math.min(dp[i + 1][j][1], dp[i + 1][j][2]) + diff, MAX);

                    nextMin = Math.min(nextMin, dp[i][j][2]);
                }
                min = nextMin;
            }
        }
        StringBuilder sb = new StringBuilder();
        int lastCons = 0, lastPos = -1;
        for (int i = 0; i < caption.length(); ++i) {
            // 逆反寻找的时候比较特殊,如果连续字符串没到达3,那么直接重复之前位置。
            if (lastCons <= 2) {
                if (lastPos == -1) {
                    lastPos = findBest(dp[i]);
                }
                ++lastCons;
                sb.append((char) (lastPos + 'a'));
            } else {
                int tmp = findBest(dp[i]);
                int curMin = Math.min(dp[i][lastPos][0], Math.min(dp[i][lastPos][1], dp[i][lastPos][2]));
                // 如果连续重复到达3个,为了最小字典序列,查看是否有更好的单词。
                if (dp[i][tmp][2] < curMin || (dp[i][tmp][2] == curMin && tmp < lastPos)) {
                    lastPos = tmp;
                    lastCons = 1;
                }
                sb.append((char) (lastPos + 'a'));
            }
        }
        return sb.toString();
    }

    private int findBest(int[][] dp) {
        int res = 0;
        for (int j = 0; j < 26; ++j) {
            if (dp[j][2] < dp[res][2]) {
                res = j;
            }
        }
        return res;
    }

    private void initialArr(int[][] row) {
        for (int i = 0; i < row.length; ++i) {
            Arrays.fill(row[i], Integer.MAX_VALUE >> 1);
        }
    }
}

补充一个Rust版本

impl Solution {
    pub fn min_cost_good_caption(caption: String) -> String {
        if caption.len() <= 2 {
            return String::new();
        }
        let caption_arr = caption.as_bytes();
        let mut dp = vec![[[std::i32::MAX / 2;3];26] ; caption.len()];
        let mut min: i32 = std::i32::MAX / 2;
        for i in (0..caption.len()).rev() {
            let offset = (caption_arr[i] - 97) as i32;
            let mut cur_min = std::i32::MAX / 2;
            if i == caption_arr.len() - 1 {
                for j in 0..26 {
                    dp[i][j][0] = (j as i32 - offset).abs();
                }
            } else {
                for j in 0..26 {
                    let diff = (j - offset).abs();
                    let j_u = j as usize;
                    dp[i][j_u][0] = dp[i][j_u][0].min(min + diff);
                    dp[i][j_u][1] = dp[i][j_u][1].min(dp[i + 1][j_u][0] + diff).min(dp[i + 1][j_u][1] + diff).min(dp[i + 1][j_u][2] + diff);
                    dp[i][j_u][2] = dp[i][j_u][2].min(dp[i + 1][j_u][1] + diff).min(dp[i + 1][j_u][2] + diff);
                    cur_min = cur_min.min(dp[i][j_u][2]);
                }
            }
            min = cur_min;
        }
        let mut res: String = String::new();
        let mut last_char = 100;
        let mut last_len = 0;
        for i in 0..caption_arr.len() {
            if last_len <= 2 {
                if last_char == 100 {
                    last_char = Self::find_min_offset(&dp[i]);
                }
                res.push(std::char::from_u32(last_char as u32 + 97).unwrap());
            } else {
                let tmp = Self::find_min_offset(&dp[i]);
                let last_cur_min = dp[i][last_char][0].min(dp[i][last_char][1]).min(dp[i][last_char][2]);
                if dp[i][tmp][2] < last_cur_min || (dp[i][tmp][2] == last_cur_min && tmp < last_char) {
                    last_char = tmp;
                    last_len = 0;
                }
                res.push(std::char::from_u32(last_char as u32 + 97).unwrap());
            }
            last_len += 1;
        }
        res
    }

    fn find_min_offset(dp: &[[i32;3];26]) -> usize {
        let mut res: usize = 0;
        for i in 0..26 {
            if dp[i][2] < dp[res][2] {
                res = i;
            }
        }
        res
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *