1.好名字 给定一个字符串,需要找出最长的连续子串(即“好名字”),满足条件:
•在这个子串中,每个字符最多出现2次 。
•并且最多只能有一个字符出现恰好2次 (其他字符只能出现0次或1次)。
换句话说:
•子串中所有字符的频率最多为2。
•并且至多有一个字符的频率为2 (其他字符频率必须≤1)。
我们需要返回最长这样的子串的长度。
方法:滑动窗口(双指针) 思路:
1.使用滑动窗口 [left, right]来代表当前子串。
2.用 freq数组(或哈希表)记录窗口内每个字符的出现次数。
3.维护两个关键计数:
•countTwo`:记录当前窗口内出现次数恰好为2的字符的个数(应该最多为1)。
•countOver:记录当前窗口内出现次数超过2的字符的个数(应该为0)。
4.扩展右指针 right,增加字符,更新频率和计数。
5.如果当前窗口违反条件(即 countOver > 0或 countTwo > 1),则收缩左指针 left直到窗口再次合法。
6.在每一步中,如果窗口合法,更新最大长度。
注意:
•由于条件要求最多一个字符出现2次,且没有字符出现超过2次,所以我们需要同时检查:
•是否有字符频率>2(countOver>0)?
•是否有超过一个字符频率恰好为2(countTwo>1)?
•当窗口非法时,收缩左边界,减少字符频率,并更新计数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 using System;public class Solution { public static int LongestGoodName (string s ) { int n = s.Length; int [] freq = new int [256 ]; int left = 0 ; int maxLen = 0 ; int countTwo = 0 ; int countOver = 0 ; for (int right = 0 ; right < n; right++) { char c = s[right]; freq[c]++; if (freq[c] == 2 ) { countTwo++; } else if (freq[c] == 3 ) { countTwo--; countOver++; } while (countOver > 0 || countTwo > 1 ) { char leftChar = s[left]; freq[leftChar]--; if (freq[leftChar] == 1 ) { countTwo--; } else if (freq[leftChar] == 2 ) { countOver--; countTwo++; } left++; } maxLen = Math.Max(maxLen, right - left + 1 ); } return maxLen; } public static void Main () { string s = Console.ReadLine(); Console.WriteLine(LongestGoodName(s)); } }
2.闪电链 你有一个闪电链攻击:
•从起点 (x0, y0)发射,初始发射范围 r0(只能攻击到发射范围内的敌人)。
•攻击到第一个敌人后,闪电链会传递到该敌人,并以该敌人为中心,以攻击范围 r继续传递(攻击到新范围内的其他敌人)。
•每个敌人只能被攻击一次。
•你只能发射一次(即只能从起点开始一次连锁)。
目标是:找出最多能攻击到的敌人数量。
注意:
•起点可能没有敌人(所以第一个敌人必须在发射范围内)。
•传递是连锁的:从起点开始,每次攻击到一个敌人后,以该敌人为中心,用攻击范围 r去攻击下一个未被攻击的敌人(类似BFS/DFS遍历)。
方法:BFS(广度优先搜索) 思路:
1.将起点视为第0个“节点”,其范围是 r0(发射范围)。
2.从起点开始,找到所有在发射范围 r0内的敌人(这些敌人可以作为第一层)。
3.然后从每一个第一层敌人开始,用攻击范围 r去扩展(BFS),寻找下一层敌人(未被攻击过的)。
4.注意:起点本身不是敌人(所以不会消耗攻击次数),但起点有发射范围。
关键点:
•由于起点是固定的,但第一层敌人可能有多个(都在发射范围内),我们需要尝试从每一个第一层敌人开始进行BFS,并取最大值(因为闪电链只能选择一条路径传递?但注意:闪电链是连锁的,但每次只能攻击一个敌人,然后从该敌人继续传递?实际上,闪电链是链式的,每次只能攻击一个目标,然后从该目标继续传递?但问题中可能允许同时攻击多个?但问题说“传递”,通常是一条链)。
但问题描述可能有两种解释:
1.链式传递 :每次攻击只能选择一个目标(从多个可选目标中选一个),然后从该目标继续传递。这样我们需要选择一条链,使得攻击到的敌人最多(类似树的最长链)。
2.范围传递 :攻击到第一个敌人后,以该敌人为中心,范围r内的所有敌人都会受到攻击(同时),然后这些敌人再同时传递?但问题说“传递”,更可能是链式(每次一个)。
由于问题说“攻击能够传递”,且“每个敌人只能受到一次攻击”,通常理解为:每次攻击只能选择一个目标(从范围内多个可选目标中选一个),然后从该目标继续。所以我们需要找到一条最长的链。
然而 ,问题要求“最多攻击敌人的数量”,且只能发射一次,但传递是链式的,所以实际上是从起点开始,选择一条链(每一步从当前节点的范围内选择一个敌人),使得链长度最长。
但注意:起点发射范围可能包含多个敌人,我们可以任意选择其中一个作为第一个攻击目标,然后从它开始传递。所以我们需要尝试所有可能的第一层敌人,然后对每个第一层敌人做BFS(实际上DFS更合适,因为要找最长链)。
方法:DFS(深度优先搜索) + 回溯 由于每一步攻击只能选择一个目标(从当前范围内的多个敌人中选一个),然后继续,所以实际上是一个决策树:我们需要选择一条链,使得攻击到的敌人最多。
但敌人数量可能不多(假设最多几十个),我们可以用DFS+回溯。
步骤:
1.表示每个敌人为节点,节点i到节点j的距离如果<=当前节点的范围(起点范围r0,其他节点范围r),则存在边。
2.从起点开始,起点可以到达所有距离<=r0的敌人。
3.然后从每个第一层敌人开始,DFS遍历(标记已访问),每次从当前敌人u,可以到达所有距离<=r且未访问的敌人v。
4.对每条路径,记录长度,取最大值。
注意:起点不是敌人,所以不计入攻击数量。
实现:
•设敌人列表为enemies(每个敌人有坐标)。
•起点为(x0,y0)。
•计算起点到每个敌人的距离,小于等于r0的为第一层。
•然后对每个第一层敌人作为起点,进行DFS(记录当前链长度,并标记访问)。
由于链是顺序的,我们需要回溯(因为一条链结束后,要尝试其他分支)。
但注意:闪电链传递时,每次只能攻击一个敌人,所以是一条路径(不是树),所以我们需要找最长的路径。
但由于图不是树(可能有环),但每个敌人只能攻击一次,所以实际上我们遍历的是简单路径。
算法:
•用DFS+回溯,尝试所有可能的路径。
•敌人数量n不大(假设n<=20),所以可行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 using System;using System.Collections.Generic;public struct Point{ public double X { get ; } public double Y { get ; } public Point (double x, double y ) { X = x; Y = y; } } public class Program { public static double Distance (Point a, Point b ) { double dx = a.X - b.X; double dy = a.Y - b.Y; return Math.Sqrt(dx * dx + dy * dy); } public static int Dfs (int u, bool [] visited, List<List<int >> graph ) { visited[u] = true ; int maxDepth = 0 ; foreach (int v in graph[u]) { if (!visited[v]) { int depth = Dfs(v, visited, graph); if (depth > maxDepth) { maxDepth = depth; } } } visited[u] = false ; return maxDepth + 1 ; } public static void Main () { string [] firstLine = Console.ReadLine().Split(); double x0 = double .Parse(firstLine[0 ]); double y0 = double .Parse(firstLine[1 ]); double r0 = double .Parse(firstLine[2 ]); double r = double .Parse(firstLine[3 ]); int n = int .Parse(Console.ReadLine()); List<Point> enemies = new List<Point>(); for (int i = 0 ; i < n; i++) { string [] coords = Console.ReadLine().Split(); double x = double .Parse(coords[0 ]); double y = double .Parse(coords[1 ]); enemies.Add(new Point(x, y)); } List<List<int >> graph = new List<List<int >>(); for (int i = 0 ; i < n; i++) { graph.Add(new List<int >()); } for (int i = 0 ; i < n; i++) { for (int j = i + 1 ; j < n; j++) { double d = Distance(enemies[i], enemies[j]); if (d <= r) { graph[i].Add(j); graph[j].Add(i); } } } Point start = new Point(x0, y0); List<int > firstLayer = new List<int >(); for (int i = 0 ; i < n; i++) { if (Distance(start, enemies[i]) <= r0) { firstLayer.Add(i); } } int ans = 0 ; bool [] visited = new bool [n]; foreach (int i in firstLayer) { int depth = Dfs(i, visited, graph); if (depth > ans) { ans = depth; } } Console.WriteLine(ans); } }
多任务调度与依赖关系 问题描述:
给定:
1.tasks数组:tasks[i]表示任务i需要的工作天数。
2.dependencies二维数组:dependencies[i][j] = 1表示任务i依赖于任务j(需要任务j完成后才能开始任务i)。
3.workers:可用工人数量(每个工人同时只能进行一个任务)。
求完成所有任务所需的最短天数。
案例说明:
•tasks = [1, 1, 3]`:任务0需要1天,任务1需要1天,任务2需要3天。
•dependencies = [[0,0,0], [1,0,0], [1,1,0]]:
•任务0:无依赖([0,0,0]表示不依赖任何任务)
•任务1:依赖任务0([1,0,0]表示只依赖任务0)
•任务2:依赖任务0和任务1([1,1,0]表示依赖任务0和任务1)
•workers = 2`:有2个工人
任务依赖图:
调度方案:
•第0天:工人1开始任务0,工人2开始任务1(但任务1依赖任务0,不能立即开始)
•实际上:只能先开始任务0(第0天)
•第1天:任务0完成,开始任务1(工人1)和任务2(工人2)?
•但任务2同时依赖任务0和任务1,必须等任务1完成
正确调度:
•第0天:开始任务0(工人1)
•第1天:任务0完成,开始任务1(工人1)
•第2天:任务1完成,开始任务2(工人1)
•总天数:1(任务0) + 1(任务1) + 3(任务2) = 5天?
但工人2一直空闲?实际上我们可以优化:
最优调度(2个工人):
•第0天:工人1开始任务0
•第1天:任务0完成,工人1开始任务1,工人2开始任务2(但任务2依赖任务1,不能开始)
•实际上任务2必须等任务1完成:
•第0天:任务0(工人1)
•第1天:任务0完成,任务1开始(工人1)
•第2天:任务1完成,任务2开始(工人1和工人2可以一起做任务2?但一个任务只能由一个工人完成)
关键约束:
•每个任务只能由一个工人完成,且不能中断
•任务必须在其所有依赖任务完成后才能开始
•一个工人同时只能进行一个任务
所以案例中:
•第0天:任务0(工人1)
•第1天:任务0完成,任务1开始(工人1)
•第2天:任务1完成,任务2开始(工人1)
•总天数:1 + 1 + 3 = 5天
算法:拓扑排序 + 动态规划 + 工人调度 步骤:
1.构建依赖图 :使用邻接表表示任务依赖关系
2.拓扑排序 :确定任务执行顺序
3.计算最早开始时间 :对于每个任务,其开始时间必须 ≥ 所有前置任务的最早完成时间
4.工人调度 :使用贪心策略,总是将可用工人分配给可以开始的任务中最早完成的
具体实现(模拟时间流): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 using System;using System.Collections.Generic;using System.Linq;public class Solution { public static int MinDays (int [] tasks, int [][] dependencies, int workers ) { int n = tasks.Length; if (n == 0 ) return 0 ; List<List<int >> graph = new List<List<int >>(); for (int i = 0 ; i < n; i++) { graph.Add(new List<int >()); } int [] inDegree = new int [n]; for (int i = 0 ; i < n; i++) { for (int j = 0 ; j < n; j++) { if (dependencies[i][j] == 1 ) { graph[j].Add(i); inDegree[i]++; } } } Queue<int > queue = new Queue<int >(); for (int i = 0 ; i < n; i++) { if (inDegree[i] == 0 ) { queue.Enqueue(i); } } int [] startTime = new int [n]; List<int > topoOrder = new List<int >(); while (queue.Count > 0 ) { int u = queue.Dequeue(); topoOrder.Add(u); foreach (int v in graph[u]) { inDegree[v]--; if (inDegree[v] == 0 ) { queue.Enqueue(v); } startTime[v] = Math.Max(startTime[v], startTime[u] + tasks[u]); } } SortedSet<int > freeWorkers = new SortedSet<int >(); for (int i = 0 ; i < workers; i++) { freeWorkers.Add(0 ); } int [] completionTime = new int [n]; foreach (int task in topoOrder) { int earliestWorkerFreeTime = freeWorkers.Min; freeWorkers.Remove(earliestWorkerFreeTime); int actualStart = Math.Max(startTime[task], earliestWorkerFreeTime); completionTime[task] = actualStart + tasks[task]; freeWorkers.Add(completionTime[task]); } return completionTime.Max(); } public static void Main () { int [] tasks = {1 , 1 , 3 }; int [][] dependencies = { new int [] {0 , 0 , 0 }, new int [] {1 , 0 , 0 }, new int [] {1 , 1 , 0 } }; int workers = 2 ; Console.WriteLine(MinDays(tasks, dependencies, workers)); } }
根据树的2中遍历顺序得到树结构 第一步:识别根节点
•前序+中序 :前序的第一个元素是根节点
•中序+后序 :后序的最后一个元素是根节点
第二步:划分左右子树
•在中序遍历中找到根节点的位置
•左子树 :根节点左边的所有元素
•右子树 :根节点右边的所有元素
第三步:确定子树遍历序列
•前序+中序 :
•左子树前序 = 前序中接下来的n个元素(n=左子树长度)
•右子树前序 = 前序中剩余的元素
•中序+后序 :
•左子树后序 = 后序中前n个元素(n=左子树长度)
•右子树后序 = 后序中接下来的元素(排除根节点)
第四步:递归处理
•对左子树和右子树分别重复上述过程
•递归终止条件 :当遍历序列为空时返回null