有 n 个筹码。第 i 个筹码的位置是 position[i] 。
我们需要把所有筹码移到同一个位置。在一步中,我们可以将第 i 个筹码的位置从 position[i] 改变为:
position[i] + 2 或 position[i] - 2 ,此时 cost = 0
position[i] + 1 或 position[i] - 1 ,此时 cost = 1
返回将所有筹码移动到同一位置上所需要的 最小代价 。
class Solution {
// 移动两步不需要代价 奇到奇 偶到偶 统计较小的移动
public int minCostToMoveChips(int[] position) {
int odd = 0;
int even = 0;
for (int n : position) {
if ((n & 1) != 0) {
odd++;
} else {
even++;
}
}
return Math.min(odd, even);
}
}
添加链接描述
实现一个 MyCalendar 类来存放你的日程安排。如果要添加的日程安排不会造成 重复预订 ,则可以存储这个新的日程安排。
当两个日程安排有一些时间上的交叉时(例如两个日程安排都在同一时间内),就会产生 重复预订 。
日程可以用一对整数 start 和 end 表示,这里的时间是半开区间,即 [start, end), 实数 x 的范围为, start <= x < end 。
实现 MyCalendar 类:
MyCalendar() 初始化日历对象。
boolean book(int start, int end) 如果可以将日程安排成功添加到日历中而不会导致重复预订,返回 true 。否则,返回 false 并且不要将该日程安排添加到日历中。
示例:
输入:
[“MyCalendar”, “book”, “book”, “book”]
[[], [10, 20], [15, 25], [20, 30]]
输出:
[null, true, false, true]
解释:
MyCalendar myCalendar = new MyCalendar();
myCalendar.book(10, 20); // return True
myCalendar.book(15, 25); // return False ,这个日程安排不能添加到日历中,因为时间 15 已经被另一个日程安排预订了。
myCalendar.book(20, 30); // return True ,这个日程安排可以添加到日历中,因为第一个日程安排预订的每个时间都小于 20 ,且不包含时间 20 。
class TreeNode {
public TreeNode left;
public TreeNode right;
public int start;
public int end;
public TreeNode(int start, int end) {
this.start = start;
this.end = end;
}
}
class MyCalendar {
// 4、二分搜索树
TreeNode root;
public MyCalendar() {
}
public boolean book(int start, int end) {
if (root == null) {
root = new TreeNode(start, end);
return true;
}
TreeNode cur = root;
while (true) { //
if (end <= cur.start) { // 左子树
if (cur.left == null) {
cur.left = new TreeNode(start, end);
return true;
}
cur = cur.left;
} else if (start >= cur.end) { // 右子树
if (cur.right == null) {
cur.right = new TreeNode(start, end);
return true;
}
cur = cur.right;
} else {
return false;
}
}
}
}
class MyCalendar3 {
// 3、TreeMap
TreeMap<Integer, Integer> map = null;
public MyCalendar3() {
map = new TreeMap<>();
// map.put(-1, -1);
// map.put((int)1e9+1, (int)1e9+1);
}
public boolean book(int start, int end) {
if (map.isEmpty()) {
map.put(start, end);
return true;
}
// Integer right = map.ceilingKey(start); //
// Integer left = map.floorKey(start);
// if (start < map.get(left) || right < end) {
// return false;
// }
Map.Entry<Integer, Integer> right = map.ceilingEntry(start);
Map.Entry<Integer, Integer> left = map.floorEntry(start);
if (right != null && right.getKey() < end || left != null && left.getValue() > start) {
return false;
}
map.put(start, end);
return true;
}
}
class MyCalendar2 {
// 2、二分查找
// TreeSet 可排序,找两个区间之间是否可插入
// 对于给定的区间[start, end),我们每次查找起点大于等于end的第一个区间[1,71),
// 同时紧挨着[1,r1)的前一个区间为[2, r2),此时如果满足r2 ≤start < end≤h1,则该区间可以预订。
// https://www.jb51.net/article/113733.htm
TreeSet<int[]> set = null;
public MyCalendar2() {
set = new TreeSet<int[]>(new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[0] - o2[0];
}
});
}
public boolean book(int start, int end) {
if (set.isEmpty()) {
set.add(new int[]{start, end});
return true;
}
int[] tmp = {end, 0};
int[] ceiling = set.ceiling(tmp); // 返回大于等于e的最小元素
if (ceiling == set.first() || set.lower(tmp)[1] <= start) {
set.add(new int[]{start, end});
return true;
}
return false;
}
}
class MyCalendar1 {
// 1、遍历检查
// 对于两个区间 [s1, e1) 和 [s2, e2),
// 如果二者没有交集,则此时应当满足 s1 > e2 或者 s2 > e1,
// 这就意味着如果满足 s1 < e2 并且 s2 < e1,则两者会产生交集
List<int[]> list = null;
public MyCalendar1() {
list = new ArrayList<int[]>();
}
public boolean book(int start, int end) {
for (int[] time : list) {
// 没有交集:start > time[1] || end < time[0]
if (start < time[1] && end > time[0]) { // 区间重复 不能添加日程
return false;
}
}
list.add(new int[]{start, end});
return true;
}
}
/**
* Your MyCalendar object will be instantiated and called as such:
* MyCalendar obj = new MyCalendar();
* boolean param_1 = obj.book(start,end);
*/
添加链接描述
你有一个单词列表 words 和一个模式 pattern,你想知道 words 中的哪些单词与模式匹配。
如果存在字母的排列 p ,使得将模式中的每个字母 x 替换为 p(x) 之后,我们就得到了所需的单词,那么单词与模式是匹配的。
(回想一下,字母的排列是从字母到字母的双射:每个字母映射到另一个字母,没有两个字母映射到同一个字母。)
返回 words 中与给定模式匹配的单词列表。
你可以按任何顺序返回答案。
示例:
输入:words = [“abc”,“deq”,“mee”,“aqq”,“dkd”,“ccc”], pattern = “abb”
输出:[“mee”,“aqq”]
解释:
“mee” 与模式匹配,因为存在排列 {a -> m, b -> e, …}。
“ccc” 与模式不匹配,因为 {a -> c, b -> c, …} 不是排列。
因为 a 和 b 映射到同一个字母。
class Solution {
// word 的每个字母需要映射到 pattern 的对应字母,并且 pattern 的每个字母也需要映射到 word 的对应字母
public List<String> findAndReplacePattern(String[] words, String pattern) {
List<String> list = new ArrayList<>();
for (String word : words) {
if (word.length() == pattern.length() && match(word, pattern) && match(pattern, word)) {
list.add(word);
}
}
return list;
}
private boolean match(String word, String pattern) {
Map<Character, Character> map = new HashMap<>();
for (int i = 0; i < word.length(); i++) {
char x = word.charAt(i);
char y = pattern.charAt(i);
if (!map.containsKey(x)) { // 没有 x,放入 x 和对应的 y
map.put(x, y);
} else if (map.get(x) != y) { // word 中的同一字母必须映射到 pattern 中的同一字母上
return false;
}
}
return true;
}
}
添加链接描述
有个内含单词的超大文本文件,给定任意两个不同的单词,找出在这个文件中这两个单词的最短距离(相隔单词数)。如果寻找过程在这个文件中会重复多次,而每次寻找的单词不同,你能对此优化吗?
示例:
输入:words = [“I”,“am”,“a”,“student”,“from”,“a”,“university”,“in”,“a”,“city”], word1 = “a”, word2 = “student”
输出:1
提示:
words.length <= 100000
class Solution {
// 从左到右遍历数组 words,当遍历到 word1 时,如果已经遍历的单词中存在 word2,
// 为了计算最短距离,应该取最后一个已经遍历到的 word2 所在的下标,计算和当前下标的距离。
public int findClosest(String[] words, String word1, String word2) {
int index1 = -1;
int index2 = -1;
int distance = words.length; // 设为最长距离
for (int i = 0; i < words.length; i++) {
String word = words[i];
if (word.equals(word1)) {
index1 = i; // 更新最后一次的位置
if (index1 != -1 && index2 != -1) {
distance = Math.min(distance, Math.abs(index1 - index2));
if (distance == 1) {
return 1; // 最短距离
}
}
} else if (word.equals(word2)) {
index2 = i;
if (index1 != -1 && index2 != -1) {
distance = Math.min(distance, Math.abs(index1 - index2));
if (distance == 1) {
return 1; // 最短距离
}
}
}
}
return distance;
}
}
添加链接描述
把字符串 s 看作是 “abcdefghijklmnopqrstuvwxyz” 的无限环绕字符串,所以 s 看起来是这样的:
"...zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd...."
现在给定另一个字符串 p 。返回 s 中 唯一 的 p 的 非空子串 的数量 。
示例 1:
输入: p = “a”
输出: 1
解释: 字符串 s 中只有一个"a"子字符。
示例 2:
输入: p = “cac”
输出: 2
解释: 字符串 s 中的字符串“cac”只有两个子串“a”、“c”。.
示例 3:
输入: p = “zab”
输出: 6
解释: 在字符串 s 中有六个子串“z”、“a”、“b”、“za”、“ab”、“zab”。
class Solution {
// substrings of p are present in s.
// 连续子串个数 不重复
public int findSubstringInWraproundString(String p) {
int[] dp = new int[26];
int k = 0; // 连续递增的子串长度 k
for (int i = 0; i < p.length(); i++) {
// 和上一个字符 dp[i - 1] 连续
// 相差 1 或 -25
if (i > 0 && (p.charAt(i) - p.charAt(i - 1) + 26) % 26 == 1) {
k++;
} else { // 重新记录连续子串长度
k = 1;
}
// 知道以某字符为结尾,和长度,就能确定这个子串。
// dp[α] 表示 p 中以字符 α 结尾且在 s 中的子串的最长长度, 知道了最长长度,也就知道了不同的子串的个数。
dp[p.charAt(i) - 'a'] = Math.max(k, dp[p.charAt(i) - 'a']);
}
return Arrays.stream(dp).sum();
}
}
添加链接描述
字符串有三种编辑操作:插入一个字符、删除一个字符或者替换一个字符。 给定两个字符串,编写一个函数判定它们是否只需要一次(或者零次)编辑。
示例 1:
输入:
first = “pale”
second = “ple”
输出: True
示例 2:
输入:
first = “pales”
second = “pal”
输出: False
class Solution {
// 如果字符相同,两个下标同时加 1。字符不同,加长的下标,如果 m == n,同时加 1,count>1 停止
public boolean oneEditAway(String first, String second) {
int m = first.length();
int n = second.length();
if (Math.abs(m - n) > 1) {
return false;
}
if (m - n >= 1) {
return oneEditAway(second, first);
}
int index1 = 0;
int index2 = 0;
int count = 0; // 记录 m == n 的不同数
while (index1 < m && index2 < n && count <= 1) { // // 超过一个不同 不能一次编辑
if (first.charAt(index1) == second.charAt(index2)) {
index1++;
index2++;
} else {
if (m == n) { // m == n 两下标都加
index1++;
}
index2++;
count++;
}
}
return count <= 1;
}
}
添加链接描述
给你由 n 个小写字母字符串组成的数组 strs,其中每个字符串长度相等。
这些字符串可以每个一行,排成一个网格。例如,strs = [“abc”, “bce”, “cae”] 可以排列为:
abc
bce
cae
你需要找出并删除 不是按字典序升序排列的 列。在上面的例子(下标从 0 开始)中,列 0(‘a’, ‘b’, ‘c’)和列 2(‘c’, ‘e’, ‘e’)都是按升序排列的,而列 1(‘b’, ‘c’, ‘a’)不是,所以要删除列 1 。
返回你需要删除的列数。
示例 1:
输入:strs = [“cba”,“daf”,“ghi”]
输出:1
解释:网格示意如下:
cba
daf
ghi
列 0 和列 2 按升序排列,但列 1 不是,所以只需要删除列 1 。
class Solution {
public int minDeletionSize(String[] strs) {
// 题目要找的是列 按列访问
int row = strs.length;
int col= strs[0].length();
int count = 0;
for (int j = 0; j < col; j++) {
for (int i = 1; i < row; i++) {
if (strs[i - 1].charAt(j) > strs[i].charAt(j)) {
count++;
break;
}
}
}
return count;
}
}
添加链接描述
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
限制:
2 <= n <= 100000
class Solution {
// 使用正负号作为标记
public int findRepeatNumber(int[] nums) {
int count = 0; // 0 - n-1 单独统计 0
for (int i = 0; i < nums.length; i++) {
if (nums[i] == 0) {
count++;
if (count >= 2) {
return 0;
}
} else {
int x = Math.abs(nums[i]);
if (nums[x] >= 0) { // 取等号
nums[x] = -nums[x];
} else {
return x; //
}
}
}
return -1;
}
// 442. 数组中重复的数据 https://leetcode.cn/problems/find-all-duplicates-in-an-array/solution/by-wj1226-lmf7/
// 将元素交换到对应的位置 此题范围是 0 - n-1
public int findRepeatNumber1(int[] nums) {
for (int i = 0; i < nums.length; i++) {
while (nums[i] != nums[nums[i]]) {
swap(nums, i, nums[i]);
}
}
for (int i = 0; i < nums.length; i++) {
if (nums[i] != i) {
return nums[i];
}
}
return -1;
}
private void swap(int nums[], int x, int y) {
int tmp = nums[x];
nums[x] = nums[y];
nums[y] = tmp;
}
}
添加链接描述
给定一个整数数组 a,其中1 ≤ a[i] ≤ n (n为数组长度), 其中有些元素出现两次而其他元素出现一次。
找到所有出现两次的元素。
你可以不用到任何额外空间并在O(n)时间复杂度内解决这个问题吗?
示例:
输入:
[4,3,2,7,8,2,3,1]
输出:
[2,3]
class Solution {
// 1、哈希 空间复杂度:O(N)
public List<Integer> findDuplicates1(int[] nums) {
HashSet<Integer> set = new HashSet<>();
ArrayList<Integer> list = new ArrayList<>();
for (int n : nums) {
if(!set.contains(n)) {
set.add(n);
}else {
list.add(n); // 重复
}
}
return list;
}
// 2、前后比较 空间复杂度:O(1)
public List<Integer> findDuplicates2(int[] nums) {
ArrayList<Integer> list = new ArrayList<>();
if(nums.length < 2) {
return list;
}
if(nums.length == 2) {
if(nums[0] == nums[1]) {
list.add(nums[0]);
}
return list;
}
Arrays.sort(nums);
for (int i = 1; i < nums.length - 1; i++) {
if(nums[i - 1] == nums[i] && nums[i] != nums[i + 1]) {
list.add(nums[i]);
}
}
// [1,1,2,3,4,5,7,9,10,10] 最后两个相等
if(nums[nums.length - 1] == nums[nums.length - 2]) {
list.add(nums[nums.length-1]);
}
return list;
}
// 3、加长度 空间复杂度:O(1)
public List<Integer> findDuplicates3(int[] nums) {
List<Integer> list = new ArrayList<>();
for (int n : nums) {
int len = nums.length;
nums[(n - 1) % nums.length] += nums.length; // 下标 加len
}
for (int i = 0; i < nums.length; i++) {
if(nums[i] > 2 * nums.length) {
list.add(i + 1);
}
}
return list;
}
// 4、将元素交换到对应的位置 空间复杂度:O(1)
// 数组的下标范围是 [0, n−1],数 i 放在数组下标为 i−1 的位置
// i 恰好出现了一次,那么将 i 放在数组中下标为 i-1
// i 出现了两次,那么我们希望其中的一个 i 放在数组下标中为 i-1 的位置
public List<Integer> findDuplicates4(int[] nums) {
for (int i = 0; i < nums.length; ++i) {
while (nums[i] != nums[nums[i] - 1]) { // 循环 直到两个元素相等或是自己
swap(nums, i, nums[i] - 1); // nums[i] 应该在 nums[i] − 1
}
}
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < nums.length; ++i) {
if (nums[i] - 1 != i) { // nums[i] 出现了两次(另一次出现在位置 num[i] − 1)
list.add(nums[i]);
}
}
return list;
}
private void swap(int[] array, int x, int y) {
int tmp = array[x];
array[x] = array[y];
array[y] = tmp;
}
// 5、使用正负号作为标记 空间复杂度:O(1)
// 对应的下标处的元素是负数,已被访问,因此该整数被第二次访问,即出现两次
public List<Integer> findDuplicates(int[] nums) {
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < nums.length; i++) {
int x = Math.abs(nums[i]); // 可能已经是负数 取绝对值
if (nums[x - 1] > 0) { // 正数:没有出现过 加符号
nums[x - 1] = -nums[x - 1];
} else { // 负数:已经出现过一次
list.add(x);
}
}
return list;
}
}
189. 轮转数组
给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
示例 1:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]
C语言:
void reverse(int* nums, int left, int right) {
while(left < right) {
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
left++;
right--;
}
}
void rotate(int* nums, int numsSize, int k){
k %= numsSize;
reverse(nums, numsSize - k, numsSize - 1);
reverse(nums, 0, numsSize - k - 1);
reverse(nums, 0, numsSize - 1);
}
java:
class Solution {
// 2、逆置
public void reverse(int[] nums, int left, int right) {
while(left < right) {
int tmp = nums[left];
nums[left++] = nums[right];
nums[right--] = tmp;
}
}
public void rotate(int[] nums, int k) {
k %= nums.length;
reverse(nums, nums.length-k, nums.length-1); // 后
reverse(nums, 0, nums.length-k-1); // 前
reverse(nums, 0, nums.length-1); // 整体
}
// 1、超出时间限制
public void rotate1(int[] nums, int k) {
k %= nums.length;
while(k != 0) {
int tmp = nums[nums.length-1];
for (int i = nums.length - 2; i >= 0; i--) {
nums[i+1] = nums[i];
}
nums[0] = tmp;
k--;
}
}
}
剑指 Offer 58 - II. 左旋转字符串
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
示例 1:
输入: s = “abcdefg”, k = 2
输出: “cdefgab”
C
void reverString(char* s, int left, int right) {
while (left < right) {
char tmp = s[left];
s[left++] = s[right];
s[right--] = tmp;
}
}
char* reverseLeftWords(char* s, int n){
int len = strlen(s);
n %= len;
reverString(s, 0, n - 1);
reverString(s, n, len - 1);
reverString(s, 0, len - 1);
return s;
}
java:
class Solution {
private void reverString(char[] array, int left, int right) {
while (left < right) {
char tmp = array[left];
array[left++] = array[right];
array[right--] = tmp;
}
}
public String reverseLeftWords(String s, int n) {
char[] array = s.toCharArray();
int len = array.length;
if (n > len) {
n %= len;
}
reverString(array, 0, n - 1); // 左
reverString(array, n, len - 1); // 右
reverString(array, 0, len - 1); // all
return new String(array);
}
}
C 语言
char* replaceSpace(char* s){
// 统计空格数
int len = strlen(s);
int count = 0;
for (int i = 0; i < len; i++) {
if (s[i] == ' ') {
count++;
}
}
// 新的数组
char* newArray = malloc(sizeof(char) * (len + count * 3 + 1));
// 记录起始
char* ans = newArray;
// 替换
while (*s != '\0') {
if (*s != ' ') {
*newArray++ = *s;
} else {
*newArray++ = '%';
*newArray++ = '2';
*newArray++ = '0';
}
s++;
}
*newArray = '\0'; // 结束
return ans; // 返回起始位置
}
Java
class Solution {
public String replaceSpace(String s) {
char[] array = new char[s.length() * 3];
int j = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) != ' ') {
array[j++] = s.charAt(i);
} else {
array[j++] = '%';
array[j++] = '2';
array[j++] = '0';
}
}
return new String(array, 0, j); // 结束位置为 j
}
public String replaceSpace2(String s) {
return s.replace(" ", "%20");
}
public String replaceSpace1(String s) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) != ' ') {
stringBuilder.append(s.charAt(i));
} else {
stringBuilder.append("%20");
}
}
return stringBuilder.toString();
}
}
添加链接描述
由范围 [0,n] 内所有整数组成的 n + 1 个整数的排列序列可以表示为长度为 n 的字符串 s ,其中:
如果 perm[i] < perm[i + 1] ,那么 s[i] == ‘I’
如果 perm[i] > perm[i + 1] ,那么 s[i] == ‘D’
给定一个字符串 s ,重构排列 perm 并返回它。如果有多个有效排列perm,则返回其中 任何一个 。
示例 1:
输入:s = “IDID”
输出:[0,4,1,3,2]
示例 2:
输入:s = “III”
输出:[0,1,2,3]
示例 3:
输入:s = “DDI”
输出:[3,2,0,1]
提示:
1 <= s.length <= 105
s 只包含字符 “I” 或 “D”
class Solution {
// 如果 s[1]='I',那么令 perm[0]=0,则无论 perm[1] 为何值都满足 perm[0]
// 如果 s[0]='D',那么令 perm[0]=n,则无论 perm[1] 为何值都满足 perm[0]>perm[1];
// s[1]='I', 令 perm[1] 为剩余数字中的最小数;如果 s[1]='D',那么令 perm[1] 为剩余数字中的最大数
public int[] diStringMatch(String s) {
int min = 0;
int max = s.length();
int[] array = new int[max + 1];
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == 'I') {
array[i] = min++;
} else {
array[i] = max--;
}
}
array[s.length()] = min; // 此时 min == max
return array;
}
}
添加链接描述
给你 root1 和 root2 这两棵二叉搜索树。请你返回一个列表,其中包含 两棵树 中的所有整数并按 升序 排序
示例 1:
输入:root1 = [2,1,4], root2 = [1,0,3]
输出:[0,1,1,2,3,4]
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
// 中序遍历 合并有序数组
public List<Integer> getAllElements(TreeNode root1, TreeNode root2) {
List<Integer> nums1 = new ArrayList<>();
List<Integer> nums2 = new ArrayList<>();
inorder(root1, nums1);
inorder(root2, nums2);
List<Integer> merged = new ArrayList<>();
int p1 = 0;
int p2 = 0;
while (true) {
// 加入另一个剩下的到结尾
if (p1 == nums1.size()) {
merged.addAll(nums2.subList(p2, nums2.size()));
break; // 注意 break
}
if (p2 == nums2.size()) {
merged.addAll(nums1.subList(p1, nums1.size()));
break;
}
if (nums1.get(p1) < nums2.get(p2)) {
merged.add(nums1.get(p1++));
} else {
merged.add(nums2.get(p2++));
}
}
return merged;
}
private void inorder(TreeNode root, List<Integer> list) {
if (root == null) {
return;
}
inorder(root.left, list);
list.add(root.val);
inorder(root.right, list);
}
}
添加链接描述
基因序列可以表示为一条由 8 个字符组成的字符串,其中每个字符都是 ‘A’、‘C’、‘G’ 和 ‘T’ 之一。
假设我们需要调查从基因序列 start 变为 end 所发生的基因变化。一次基因变化就意味着这个基因序列中的一个字符发生了变化。
例如,“AACCGGTT” --> “AACCGGTA” 就是一次基因变化。
另有一个基因库 bank 记录了所有有效的基因变化,只有基因库中的基因才是有效的基因序列。
给你两个基因序列 start 和 end ,以及一个基因库 bank ,请你找出并返回能够使 start 变化为 end 所需的最少变化次数。如果无法完成此基因变化,返回 -1 。
注意:起始基因序列 start 默认是有效的,但是它并不一定会出现在基因库中。
示例 1:
输入:start = “AACCGGTT”, end = “AACCGGTA”, bank = [“AACCGGTA”]
输出:1
示例 2:
输入:start = “AACCGGTT”, end = “AAACGGTA”, bank = [“AACCGGTA”,“AACCGCTA”,“AAACGGTA”]
输出:2
示例 3:
输入:start = “AAAAACCC”, end = “AACCCCCC”, bank = [“AAAACCCC”,“AAACCCCC”,“AACCCCCC”]
输出:3
提示:
start.length == 8
end.length == 8
0 <= bank.length <= 10
bank[i].length == 8
start、end 和 bank[i] 仅由字符 [‘A’, ‘C’, ‘G’, ‘T’] 组成
class Solution {
public int minMutation(String start, String end, String[] bank) {
Set<String> gene = new HashSet<String>(); // 检测是否在基因库 bank 中
Set<String> visited = new HashSet<String>(); // 判断是否遍历过
char[] keys = {'A', 'C', 'G', 'T'};
for (String b : bank) { // 基因库加入 gene
gene.add(b);
}
if (start.equals(end)) { // start 和 end 相等,直接返回 0
return 0;
}
if (!gene.contains(end)) { // end 不在 bank 中,无法实现目标变化,返回 -1
return -1;
}
Queue<String> queue = new LinkedList<>(); // 可能变换的基因序列
queue.offer(start);
visited.add(start);
int count = 1; // 返回的次数
while (!queue.isEmpty()) {
int sz = queue.size(); // 每次拿出队列所有
for (int i = 0; i < sz; i++) {
String cur = queue.poll();
// 变化一次最多可能会生成 3×8=24 种不同的基因序列
for (int j = 0; j < 8; j++) { // 遍历每个序列的字符
for (int k = 0; k < 4; k++) { // 尝试 A C G T
if (cur.charAt(j) != keys[k]) {
StringBuilder stringBuilder = new StringBuilder(cur);
stringBuilder.setCharAt(j, keys[k]); // j 的位置改成 keys[k]
String next = stringBuilder.toString();
if (!visited.contains(next) && gene.contains(next)) { // 合法且未遍历过
if (next.equals(end)) { // 此次变化变成了 end,返回次数
return count;
}
queue.offer(next); // 合法且未遍历过 加入队列
visited.add(next); // 标记已被遍历过
}
}
}
}
}
count++; // 一次基因变化
}
return -1; // 队列中所有的元素都已经遍历完成还无法变成 end,则此时无法实现目标变化,返回 -1
}
}
添加链接描述
给你一个整数数组 nums 和一个整数 k ,请你返回子数组内所有元素的乘积严格小于 k 的连续子数组的数目。
示例 1:
输入:nums = [10,5,2,6], k = 100
输出:8
解释:8 个乘积小于 100 的子数组分别为:[10]、[5]、[2],、[6]、[10,5]、[5,2]、[2,6]、[5,2,6]。
需要注意的是 [10,5,2] 并不是乘积小于 100 的子数组。
示例 2:
输入:nums = [1,2,3], k = 0
输出:0
提示:
1 <= nums.length <= 3 * 104
1 <= nums[i] <= 1000
0 <= k <= 106
class Solution {
// 时间复杂度:O(N)
// 注意:连续子序列(包含一个数) -> 滑动窗口
// 左右指针:left 从 0 开始,循环 right,
// 符合条件加上左到右的自序列:r-l+1
// 如果大于 k,left 往右移(可能不止一次),并且去掉 left 的值,判断下一段
public int numSubarrayProductLessThanK(int[] nums, int k) {
int i = 0;
int prod = 1;
int count = 0;
for (int j = 0; j < nums.length; j++) {
prod *= nums[j];
while (i <= j && prod >= k) { // [1,2,3] 0 当前窗口没有符合条件
prod /= nums[i];
i++;
}
count += j - i + 1;
}
return count;
}
}
添加链接描述
共有 n 名小伙伴一起做游戏。小伙伴们围成一圈,按 顺时针顺序 从 1 到 n 编号。确切地说,从第 i 名小伙伴顺时针移动一位会到达第 (i+1) 名小伙伴的位置,其中 1 <= i < n ,从第 n 名小伙伴顺时针移动一位会回到第 1 名小伙伴的位置。
游戏遵循如下规则:
从第 1 名小伙伴所在位置 开始 。
沿着顺时针方向数 k 名小伙伴,计数时需要 包含 起始时的那位小伙伴。逐个绕圈进行计数,一些小伙伴可能会被数过不止一次。
你数到的最后一名小伙伴需要离开圈子,并视作输掉游戏。
如果圈子中仍然有不止一名小伙伴,从刚刚输掉的小伙伴的 顺时针下一位 小伙伴 开始,回到步骤 2 继续执行。
否则,圈子中最后一名小伙伴赢得游戏。
给你参与游戏的小伙伴总数 n ,和一个整数 k ,返回游戏的获胜者。
class Solution {
// 1、模拟 队列
// k-1 个队尾重新加入,当前队首元素就是要离开的,出队列,此时顺时针的下一个就是队首
public int findTheWinner1(int n, int k) {
Queue<Integer> queue = new LinkedList<>();
for (int i = 1; i <= n; i++) {
queue.offer(i);
}
while (queue.size() > 1) {
for (int i = 1; i < k; i++) { // 从 1 开始
queue.offer(queue.poll()); // poll
}
queue.poll();
}
return queue.poll();
}
// 2、循环
// (pos + m) % 当前轮次的人数
public int findTheWinner2(int n, int k) {
int pos = 0;
for (int i = 2; i <= n; i++) {
pos = (pos + k) % i;
}
return pos + 1;
}
// 3、递归
// n−1 个人的游戏是从原来第 k+1 个人开始
// 偏差 k
// 从 0 到 n-1 去掉 1 的偏差 最终加一次 1
// f(n,k) = (f(n−1, k) + k) % n
public int findTheWinner(int n, int k) {
if (n == 1) {
return 1;
}
return (k + findTheWinner(n - 1, k) - 1) % n + 1; // 从 0 开始
}
}
使用 SimpleDateFormat 格式化日期
SimpleDateFormat 是一个以语言环境敏感的方式来格式化和分析日期的类。SimpleDateFormat 允许你选择任何用户自定义日期时间格式来运行。
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestDemo {
public static void main(String[] args) {
Date date= new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyy-mm-dd hh:mm:ss");
System.out.println(simpleDateFormat.format(date)); // 2022-18-04 07:18:15
}
public static void main1(String[] args) {
// 初始化 Date 对象
Date date = new Date();
// 使用 toString() 函数显示日期时间
System.out.println(date.toString()); // Wed May 04 19:20:28 CST 2022
}
}
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
public class Main {
public static void main(String[] args) {
String filePath = "f:/test.txt";
String str = "hello";
System.out.println(countStr(filePath, str));
}
private static int countStr(String filePath, String str) {
// 获取文件内容
StringBuilder stringBuilder = new StringBuilder();
try (Reader reader = new FileReader(filePath)) {
while (true) {
char[] buffer = new char[1024];
int len = reader.read(buffer);
if (len == -1) {
break;
}
String s = new String(buffer, 0, len);
stringBuilder.append(s);
}
} catch (IOException e) {
e.printStackTrace();
}
// System.out.println(stringBuilder);
// 统计次数
int count = 0;
while (stringBuilder.length() >= str.length()) {
int index = stringBuilder.indexOf(str);
if (index > -1) { // 有这个字符串
stringBuilder = stringBuilder.delete(index, index + str.length());
count++;
} else {
break;
}
}
return count;
}
}
描述:
给你一个日志数组 logs。每条日志都是以空格分隔的字串,其第一个字为字母与数字混合的 标识符 。
有两种不同类型的日志:
请按下述规则将日志重新排序:
返回日志的最终顺序。
示例 1:
输入:logs = [“dig1 8 1 5 1”,“let1 art can”,“dig2 3 6”,“let2 own kit dig”,“let3 art zero”]
输出:[“let1 art can”,“let3 art zero”,“let2 own kit dig”,“dig1 8 1 5 1”,“dig2 3 6”]
解释:
字母日志的内容都不同,所以顺序为 “art can”, “art zero”, “own kit dig” 。
数字日志保留原来的相对顺序 “dig1 8 1 5 1”, “dig2 3 6” 。
示例 2:
输入:logs = [“a1 9 2 3 1”,“g1 act car”,“zo4 4 7”,“ab1 off key dog”,“a8 act zoo”]
输出:[“g1 act car”,“a8 act zoo”,“ab1 off key dog”,“a1 9 2 3 1”,“zo4 4 7”]
提示:
1 <= logs.length <= 100
3 <= logs[i].length <= 100
logs[i] 中,字与字之间都用 单个 空格分隔
题目数据保证 logs[i] 都有一个标识符,并且在标识符之后至少存在一个字
链接: https://leetcode-cn.com/problems/reorder-data-in-log-files/
class Solution {
public String[] reorderLogFiles(String[] logs) {
Arrays.sort(logs, new Comparator<String>() {
@Override
public int compare(String log1, String log2) {
// 以标志符分成两部分 标志符 和 内容串
String[] split1 = log1.split(" ", 2);
String[] split2 = log2.split(" ", 2);
// 判断是数字日志还是字母日志 比较内容串的第一个即可
boolean isDigit1 = Character.isDigit(split1[1].charAt(0));
boolean isDigit2 = Character.isDigit(split2[1].charAt(0));
if (isDigit1 && isDigit2) { // 都是数字
return 0;
} else if (!isDigit1 && !isDigit2) { // 都是字母
int cmp = split1[1].compareTo(split2[1]); // 比较内容串 split1>split2 返回>0,等于返回0,小于返回<1
if (cmp != 0) return cmp;
return split1[0].compareTo(split2[0]); // 内容串一样 比较标志符
} else { // 有一个是字母
return isDigit1 ? 1 : -1; // split1 是数字,返回 1,split2 是字母,放前面
}
}
});
return logs;
}
}
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
示例 1:
输入:
[“CQueue”,“appendTail”,“deleteHead”,“deleteHead”]
[[],[3],[],[]]
输出:[null,null,3,-1]
链接:添加链接描述
class CQueue {
Stack<Integer> stack1;
Stack<Integer> stack2;
public CQueue() {
stack1 = new Stack<>();
stack2 = new Stack<>();
}
// 尾部插入
public void appendTail(int value) {
stack1.push(value);
}
// 头部删除
public int deleteHead() {
if (stack1.empty() && stack2.empty()) {
return - 1;
}
if (stack2.empty()) {
while (!stack1.empty()) {
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
}
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
示例:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.min(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.min(); --> 返回 -2.
提示:
各函数的调用总次数不超过 20000 次
链接:添加链接描述
class MinStack {
/** initialize your data structure here. */
Stack<Integer> stack;
Stack<Integer> minStack;
public MinStack() {
stack = new Stack<>();
minStack = new Stack<>();
}
public void push(int x) {
stack.push(x);
if (!minStack.empty()) {
if (x <= minStack.peek()) { // 小于等于
minStack.push(x);
}
} else {
minStack.push(x);
}
}
public void pop() {
int top = stack.pop();
if (!minStack.empty()) {
if (top == minStack.peek()) {
minStack.pop();
}
}
}
public int top() {
return stack.peek();
}
public int min() {
return minStack.peek();
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(x);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.min();
*/