Week1&2
week1
面试题 17.05. 字母与数字
给定一个放有字母和数字的数组,找到最长的子数组,且包含的字母和数字的个数相同。
返回该子数组,若存在多个最长子数组,返回左端点下标值最小的子数组。若不存在这样的数组,返回一个空数组。
示例 1:
输入: [“A”,“1”,“B”,“C”,“D”,“2”,“3”,“4”,“E”,“5”,“F”,“G”,“6”,“7”,“H”,“I”,“J”,“K”,“L”,“M”]
输出: [“A”,“1”,“B”,“C”,“D”,“2”,“3”,“4”,“E”,“5”,“F”,“G”,“6”,“7”]
示例 2:
输入: [“A”,“A”]
输出: []
暴力求解:超出时间限制
public String[] findLongestSubarray(String[] array) {
int gap = 0, len = 0;
int start = 0, end = 0;
for (int i = 0; i < array.length - 1; i++) {
int left = array.length, right = -1;
gap = 0;
for (int j = i; j < array.length; j++) {
if (Character.isLetter(array[j].charAt(0))) { // 字母
gap += 1;
left = Math.min(left, j);
right = Math.max(right, j);
}else {
gap -= 1;
left = Math.min(left, j);
right = Math.max(right, j);
}
if (gap == 0 && (right - left) > len){
len = right - left;
start = left;
end = right;
}
}
}
int length = len + 1;
if (length % 2 == 0){
String[] res = new String[length];
int index = 0;
for (int i = start; i <= end; i++) {
res[index++] = array[i];
}
return res;
}
return new String[0];
}
采用前缀和+Map思想求解:AC
核心要点:为了在转换后的数组中寻找元素和为 0的子数组,可以计算转换后的数组的前缀和,如果两个下标对应的前缀和相等,则这两个下标之间的子数组的元素和为 0。
如果同一个前缀和出现多次,则该前缀和对应的最长子数组的长度为该前缀和的第一次出现的下标与最后一次出现的下标之间的子数组,因此为了在转换后的数组中寻找元素和为 0 的最长子数组,需要记录每个前缀和第一次出现的下标。使用哈希表记录每个前缀和第一次出现的下标。由于空前缀的前缀和是 0 且对应下标 −1,因此首先将前缀和 0 与下标 −1 存入哈希表。
public String[] findLongestSubarray(String[] array) {
Map<Integer, Integer> map = new HashMap<>();
map.put(0, -1);
int sum = 0, length = 0, start = -1, len = array.length;
for (int i = 0; i < len; i++) {
if (Character.isLetter(array[i].charAt(0))){
// 字母
sum++;
}else {
sum--;
}
if (map.containsKey(sum)){ // 有前缀和为sum的序列
int first = map.get(sum);
if ((i-first) > length){
length = i - first;
start = first + 1;
}
}else {
map.put(sum, i);
}
}
if (length == 0){
return new String[0];
}
return Arrays.copyOfRange(array, start, start + length);
}
项目问题总结
yml文件中中文报错的问题,将yml文件格式改为utf8注意点击的是convert,不要以管理员身份启动项目!!
1282. 用户分组
用户分组题目描述
想到使用Map集合来记录元素和对应的下标位置但是没想到Map.getOrDefault的这种用法
Map.getOrDefault(key,默认值):
Map中会存储一一对应的key和value
如果 在Map中存在key,则返回key所对应的的value
如果 在Map中不存在key,则返回默认值
这个思路可以说是非常miao了~
public List<List<Integer>> groupThePeople(int[] groupSizes) {
List<List<Integer>> ans = new ArrayList<>();
Map<Integer, List<Integer>> map = new HashMap<>();
for (int i = 0; i < groupSizes.length; i++) {
/**
* Map.getOrDefault(key,默认值):
*
* Map中会存储一一对应的key和value
* 如果 在Map中存在key,则返回key所对应的的value
* 如果 在Map中不存在key,则返回默认值
*/
// 输入:groupSizes = [3,3,3,3,3,1,3]
// 输出:[[5],[0,1,2],[3,4,6]]
List<Integer> list = map.getOrDefault(groupSizes[i], new ArrayList<>());
list.add(i); // 下标
if (list.size() == groupSizes[i]) { // 放满了就新建
ans.add(list); // new ArrayList<>(list)
list = new ArrayList<>();
}
map.put(groupSizes[i], list);
}
return ans;
}
2383. 赢得比赛需要的最少训练时长
题目详情:赢得比赛需要的最少训练时长
简单题,直接上代码:
public int minNumberOfHours(int initialEnergy, int initialExperience, int[] energy, int[] experience) {
int ans1 = 0, ans2 = 0;
// 能量
for (int x : energy) {
if (x > initialEnergy){
ans1 += (x-initialEnergy);
initialEnergy += (x-initialEnergy);
}
initialEnergy -= x;
if (initialEnergy == 0) {
ans1++;
initialEnergy++;
}
}
// 经验
for (int i = 0; i < experience.length; i++) {
if (experience[i] >= initialExperience) {
ans2 += (experience[i] - initialExperience);
ans2++;
initialExperience += (experience[i] - initialExperience) + 1;
}
initialExperience += experience[i];
}
return ans1 + ans2;
}
项目问题
用户在登录状态下,若管理人员对数据库内容进行了修改,而用户前端界面又使用到了缓存,记得要让用户退出系统后重新登陆以获取新的数据信息和缓存信息
动态路由
vue实现动态路由
vite + vue3实现动态路由:
// 图标全局引入
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
// 注意:刷新页面会导致页面路由重置
export const setRoutes = (menus) => {
if (!menus || !menus.length) {
const manager = localStorage.getItem('manager')
if (!manager) {
return
}
menus = JSON.parse(manager).managerInfo.menus
}
if (menus.length) {
// 开始渲染 未来的不确定的 用户添加的路由
menus.forEach(item => { // 所有的页面都需要设置路由,而目录不需要设置路由
if (item.path) { // 当且仅当path不为空的时候才去设置路由
router.addRoute('Layout', { path: item.path, name: item.page, component: modules['../views/' + item.page + '.vue'] })
} else {
if (item.children && item.children.length) {
item.children.forEach(sub => {
if (sub.path) {
router.addRoute('Layout', { path: sub.path, name: sub.page, component: modules['../views/' + sub.page + '.vue'] })
}
})
}
}
})
}
}
setRoutes()
// 路由守卫
router.beforeEach((to, from, next) => {
const store = useUserStore() // 拿到用户对象信息
const user = store.managerInfo.user
const hasUser = user && user.id
const noPermissionPaths = ['/login', '/404'] // 定义无需登录的路由
if (!hasUser && !noPermissionPaths.includes(to.path)) { // 用户没登录, 假如你当前跳转login页面,然后login页面没有用户信息,这个时候你再去往 login页面跳转,就会发生无限循环跳转
// 获取缓存的用户数据
// 如果to.path === '/login' 的时候 !noPermissionPaths.includes(to.path) 是返回 false的,也就不会进 next("/login")
next("/login")
} else {
if (!to.matched.length) {
next('/404')
} else {
next()
}
}
})
export default router
菜单:
<div style="display: flex">
<div style="width: 200px; min-height: calc(100vh - 60px); border-right: 1px solid #ccc">
<el-menu
:default-active="'home'"
:default-openeds="menus.map(v => v.id + '')"
class="el-menu-demo"
style="border: none"
router
>
<div v-for="item in menus" :key="item.id">
<div v-if="item.type === 2">
<el-menu-item :index="item.path" v-if="!item.hide">
<!-- <el-icon v-if="item.icon">< {{ item.icon }} /></el-icon>-->
<span>{{ item.name }}</span>
</el-menu-item>
</div>
<div v-else>
<el-sub-menu :index="item.id + ''" v-if="!item.hide">
<template #title>
<!-- <el-icon v-if="item.icon"><{{ item.icon }} /></el-icon>-->
<span>{{ item.name }}</span>
</template>
<div v-for="subItem in item.children" :key="subItem.id">
<el-menu-item :index="subItem.path" v-if="!subItem.hide">
<template #title>
<!-- <el-icon v-if="subItem.icon">< {{ subItem.icon }} /></el-icon>-->
<span>{{ subItem.name }}</span>
</template>
</el-menu-item>
</div>
</el-sub-menu>
</div>
</div>
</el-menu>
</div>
404页面:
<template>
<div style="text-align: center">
<h1 style="font-size: 200px">404</h1>
<h2>未找到页面</h2>
<div><a href="/">返回主页</a></div>
</div>
</template>
注意:上面的代码是vue2的实现方式
在vite + vue3的代码里,动态路由的渲染变了!
// v2: component: () => import('../layout/Layout.vue')
// v3: component: modules['../views/Home.vue']
// 需要定义 modules所对应的目录:const modules = import.meta.glob('../views/*.vue')
1605. 给定行和列的和求可行矩阵
好题:中等难度已知矩阵的行和和列和求矩阵:题目描述
毫无思路的一道题目,开始对2x2的矩阵进行了模拟发现可以利用解方程求解但对于高维度的矩阵此方法并不适用就没有了头绪
public int[][] restoreMatrix(int[] rowSum, int[] colSum) {
// 贪心的思想:行和与列和都知道是毕竟有一交叉元素,根据此特性进行维护
// 先使得行或列的最小值填入矩阵让一个和为0 此时对另一个剩余和进行维护
// 直至行和与列和都为 0 结束
int[][] matrix = new int[rowSum.length][colSum.length];
int row = 0, col = 0;
while (row < rowSum.length && col < colSum.length){
matrix[row][col] = Math.min(rowSum[row], colSum[col]);
rowSum[row] -= matrix[row][col];
colSum[col] -= matrix[row][col];
/**
* 减去两者的小者毕竟会有一个行或列和为 0
* 让和为 0 的行或列下标后移继续遍历 同时保持另一位置下标索引不变
* 直至其和为 0
*/
if (rowSum[row] == 0){
row++;
}
if (colSum[col] == 0){
col++;
}
}
return matrix;
}
回溯专题
组合型回溯问题
77. 组合
class Solution {
List<List<Integer>> ans = new ArrayList<>();
List<Integer> list = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
dfs(1, n, k);
return ans;
}
/**
* 选择区间为 [cur, n]
* @param current 当前位置是 current
* @param n 序列总长度为 n
* @param k 需要组合选择的数字个数为 k
*/
public void dfs(int current, int n, int k) {
/**
* list.size() 为当前已选择的长度
* 区间为 [cur, n] 则表示共有 n - current + 1 个数字可供选择
* 二者相加即代表该序列能选择的总长度
* 如果该长度小于 k 一位置没法选择出长度为 k 的序列,直接跳出结束
*/
if (list.size() + (n - current + 1) < k) {
return;
}
// 代表刚好找到了长度为 k 的序列
if (list.size() == k) {
// 刚好有k个选择项
ans.add(new ArrayList<>(list));
return;
}
// 以下状态表示长度还没到 k 但是查获毒可以到 k, 我们就继续找
list.add(current);
dfs(current + 1, n, k);
// 回溯到不加入该节点前的状态
list.remove(list.size() - 1);
dfs(current + 1, n, k);
}
}
216. 组合总和 III
class Solution {
List<List<Integer>> ans = new ArrayList<>();
List<Integer> list = new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
dfs(1, n, k);
return ans;
}
public void dfs(int cur, int n, int k) {
int sum = getSum(list);
// cur 若为 9 加一后变为 10 此时不能直接返回应为 10 会有回溯到 9 可行解的可能
if (list.size() + (n - cur + 1) < k || list.size() > k || cur > 10 || sum > n) {
return;
}
if (list.size() == k && sum == n) {
ans.add(new ArrayList<>(list));
return;
}
list.add(cur);
dfs(cur+1, n, k);
list.remove(list.size()-1);
dfs(cur+1, n, k);
}
private int getSum(List<Integer> list) {
int sum = 0;
for (Integer x : list) {
sum += x;
}
return sum;
}
}
22. 括号生成
非常经典的一道题目,题目详情见 -> here
附一个非常秒的讲解,大佬讲的非常nice!!!大佬讲解
class Solution {
List<String> ans = new ArrayList<>();
public List<String> generateParenthesis(int n) {
if (n == 0) {
return ans;
}
StringBuilder sb = new StringBuilder();
dfs(sb, n, n);
return ans;
}
/**
* @param cur 当前递归得到的结果
* @param left 左括号还有几个可以使用
* @param right 右括号还有几个可以使用
*/
private void dfs(StringBuilder cur, int left, int right) {
if (left == right && left == 0) {
// 都没有可用括号了
ans.add(cur.toString());
return;
}
// 左括号
if (left > right) {
// 不可能出现左比右多的情况 极限情况是二者相等
return;
}
if (left > 0) {
cur.append('(');
dfs(cur, left-1, right);
cur.deleteCharAt(cur.length()-1); // 回溯
}
// 右括号
if (right > 0) {
cur.append(')');
dfs(cur, left, right-1);
cur.deleteCharAt(cur.length()-1); // 回溯
}
}
}
子集型回溯问题
17. 电话号码的字母组合
电话号码的字母组合题目详情
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
解法不用脑子也能想到(转自LeetCode王尼玛)见:
class Solution {
List<String> res = new ArrayList<>();
String[] letter_map = {" ","*","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public List<String> letterCombinations(String digits) {
if (digits.length() == 0) {
return res;
}
dfs(digits, new StringBuilder(), 0);
return res;
}
/**
* @param digits 原输入字符串
* @param cur 当前字符串
* @param index digits中遍历到的下标位置
*/
public void dfs(String digits, StringBuilder cur, int index) {
if (index == digits.length()) {
res.add(cur.toString());
return;
}
char ch = digits.charAt(index);
int index_map = ch - '0'; // 转为int下标
String map = letter_map[index_map];
// 把最后下标每个对应元素挨个往里加,然后回溯到上一位下标位置直到开始处
for (int i = 0; i < map.length(); i++) {
cur.append(map.charAt(i));
dfs(digits, cur, index + 1);
cur.deleteCharAt(cur.length() - 1);
}
}
}
78
131
前缀和 + Map
一般用于求解子数组问题,套路在这
2488. 统计中位数为 K 的子数组
public int countSubarrays(int[] nums, int k) {
int index = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] == k) {
index = i;
break;
}
}
// 用于记录大于k的元素个数与小于k的元素个数之差 大于的用正号表示小于负号
// 前缀和比k大的+1 小的-1
Map<Integer, Integer> map = new HashMap<>();
int ans = 0, preSum = 0;
map.put(0, 1);
for (int i = 0; i < nums.length; i++) {
if (nums[i] - k != 0) {
preSum += (nums[i] - k) > 0 ? 1 : -1; // 个数差
}
if (i < index) {
// 还没到 k 所在位置即没有右边元素
map.put(preSum, map.getOrDefault(preSum, 0) + 1);
}else { // 到右边了可以找个数了
int left = map.getOrDefault(preSum, 0);
/**
* 因为个数为偶数时取左边元素为中位数,所以算上k在右边少取一个即可
* 例如:如下 k == 4
* 6 5 2 4 7 3 可知左边前缀和为 2-1 = 1
* 此时算上了 k 即 4 在其右边只需找到前缀和为 0 序列即可
* 右边 1 - 1 = 0, 此时排序后写刚好为
* 2 3 4 5 6 7 中位数为 4
*/
int right = map.getOrDefault(preSum - 1, 0);
ans += (left + right);
}
}
return ans;
}
2389. 和有限的最长子序列
public int[] answerQueries(int[] nums, int[] queries) {
Arrays.sort(nums);
int[] ans = new int[queries.length];
out:
for (int i = 0; i < queries.length; i++) {
int sum = 0;
for (int j = 0; j < nums.length; j++) {
sum += nums[j];
if (sum <= queries[i]) {
ans[i]++;
} else {
continue out;
}
}
}
return ans;
}
其实看到题目的第一眼就想到了这dior题又和昨天一样可以用前缀和解决,代码优化如下(多看一眼就会爆炸!!!):
class Solution {
public int[] answerQueries(int[] nums, int[] queries) {
Arrays.sort(nums);
int[] ans = new int[queries.length];
int[] preSum = new int[nums.length + 1];
for (int i = 0; i < nums.length; i++) {
preSum[i + 1] = preSum[i] + nums[i];
}
// 在 preSum 中找下标即可
for (int i = 0; i < queries.length; i++) {
/**
* 这里为啥不能直接使用Arrays中的二分查找方法呢??
* 因为 preSum 的第一个元素值为 0
* 我们要去掉这个值 说白了要取下标范围为 [1, preSum.length-1] 这个范围的数组
*/
ans[i] = binarySearch(preSum, queries[i]) - 1;
}
return ans;
}
/**
* 遍历数组假设当前查询值为 target
* 使用二分查找获取满足在 nums 数组中 nums[index] > target 的 首个index
* 在处理结果时将 index-1 赋值给 ans 即可
* 因为 index-1 是数组中满足和小于等于 query 的最大下标
* 而由于有初始元素 0 的存在占用了一个下标,所以此处下标不需再加一
*/
public int binarySearch(int[] nums, int target) {
int low = 1, high = nums.length;
while (low < high) {
int mid = low + (high - low) / 2;
if (nums[mid] > target) {
high = mid;
} else {
low = mid + 1;
}
}
return low;
}
/** 本质一样
public int binarySearch(int[] nums, int target) {
int low = 0, high = nums.length - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (nums[mid] > target) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return low;
}
*/
}
week2
1012. 至少有 1 位重复的数字
困难:题目描述,要求时间复杂度不能为n²
暴力求解,超出时间限制:
class Solution {
public int numDupDigitsAtMostN(int n) {
if (n <= 10) {
return 0;
}
HashSet<Integer> set = new HashSet<>();
int ans = 0;
for (int i = 11; i <= n; i++) {
if (up1(i)) {
ans++;
}
}
return ans;
}
public boolean up1(int num) {
int temp = num;
int[] cnts = new int[10];
while (temp > 0) {
cnts[temp % 10]++;
temp /= 10;
}
Arrays.sort(cnts);
return cnts[cnts.length-1] > 1;
}
}
正确方法采用组合数学(没看懂)或数位DP,数位dp点我
1626. 无矛盾的最佳球队
中等:题目描述
思路非常简单,对数组进行排序找得分最高的,同时双指针遍历当前位置前面的所有运动员去比较年龄,在分数升序的情况下对年龄进行判断进而维护dp数组求解只因可!
/* 典型的动态规划背包问题 */
public int bestTeamScore(int[] scores, int[] ages) {
int n = scores.length;
int[][] people = new int[n][2]; // 用于记录队员的状态
for (int i = 0; i < n; i++) {
people[i] = new int[]{ scores[i], ages[i] };
}
// 将所有队员按照分数升序进行排序,分数相同时,则按照年龄升序进行排序
Arrays.sort(people, (p1, p2) -> p1[0] != p2[0] ? (p1[0]-p2[0]) : (p1[1]-p2[1]));
int[] dp = new int[n];
int res = 0;
for (int i = 0; i < n; i++) {
for (int j = i - 1; j >= 0; j--) {
if (people[j][1] <= people[i][1]) { // 年龄情况battle
// 如果当前人的年龄比上一位年纪大, 记录下二者中得分较高的一位
dp[i] = Math.max(dp[i], dp[j]);
}
}
// 如果不满足上面的if, 数组是根据分数升序排序的, 当前的得分一定更高, 我们直接加即可
dp[i] += people[i][0];
res = Math.max(res, dp[i]);
}
return res;
}
1603. 等差子数组
中等:题目描述就是在一个序列中找到满足经过一定变换的等差子序列数组
名义上的中等题实际难度不大,无脑解题直接上代码:
class Solution {
public List<Boolean> checkArithmeticSubarrays(int[] nums, int[] l, int[] r) {
int len = l.length;
List<Boolean> ans = new ArrayList<>();
for (int i = 0; i < len; i++) {
ans.add(isDifferential(nums, l[i], r[i]));
}
return ans;
}
public boolean isDifferential(int[] nums, int start, int end) {
if (end - start == 1) {
return true;
}
int[] temp = Arrays.copyOfRange(nums, start, end + 1);
Arrays.sort(temp);
int dif = temp[1] - temp[0];
for (int i = 0; i < temp.length - 1; i++) {
if (temp[i+1] - temp[i] != dif) {
return false;
}
}
return true;
}
}