namespace CNBlogs.DesignPattern.Common { public class Factory { public ICar GetCar(CarType carType) { switch (carType) { case CarType.SportCarType: return new SportCar(); case CarType.JeepCarType: return new JeepCar(); case CarType.HatchbackCarType: return new HatchbackCar(); default: throw new Exception("爱上一匹野马,可我的家里没有草原. 你走吧!"); } } } }
//基础接口 public interface Component { public void biu(); } //具体实现类 public class ConcretComponent implements Component {
public void biu() { System.out.println("biubiubiu"); } } //装饰类 public class Decorator implements Component {
public Component component; public Decorator(Component component) { this.component = component; } public void biu() { this.component.biu(); } } //具体装饰类 public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component); }
public void biu() { System.out.println("ready?go!"); this.component.biu(); } }
/** * logger decorator for other extension * this class have no specific implementation * just for a decorator definition * @author jzb * */ public class DecoratorLogger implements Logger {
public Logger logger;
public DecoratorLogger(Logger logger) {
this.logger = logger; } @Override public void error(String str) {}
@Override public void info(String str) {} //省略其他默认实现 }
if ((pid=fork())<0) { printf("fork error!"); }elseif (pid==0) { printf("The child process PID is %d.\n",getpid()); printf("The Group ID is %d.\n",getpgrp()); printf("The Group ID is %d.\n",getpgid(0)); printf("The Group ID is %d.\n",getpgid(getpid())); exit(0); }
sleep(3); printf("The parent process PID is %d.\n",getpid()); printf("The Group ID is %d.\n",getpgrp());
if ((pid=fork())<0) { printf("fork error!"); exit(1); }elseif (pid==0) { printf("The child process PID is %d.\n",getpid()); printf("The Group ID of child is %d.\n",getpgid(0)); // 返回组id sleep(5); printf("The Group ID of child is changed to %d.\n",getpgid(0)); exit(0); }
sleep(1); setpgid(pid,pid); // 改变子进程的组id为子进程本身 sleep(5); printf("The parent process PID is %d.\n",getpid()); printf("The parent of parent process PID is %d.\n",getppid()); printf("The Group ID of parent is %d.\n",getpgid(0)); setpgid(getpid(),getppid()); // 改变父进程的组id为父进程的父进程 printf("The Group ID of parent is changed to %d.\n",getpgid(0));
if ((pid=fork())<0) { printf("fork error!"); exit(1); }elseif (pid==0) { printf("The child process PID is %d.\n",getpid()); printf("The Group ID of child is %d.\n",getpgid(0)); printf("The Session ID of child is %d.\n",getsid(0)); sleep(10); setsid(); // 子进程非组长进程,故其成为新会话首进程,且成为组长进程。该进程组id即为会话进程 printf("Changed:\n"); printf("The child process PID is %d.\n",getpid()); printf("The Group ID of child is %d.\n",getpgid(0)); printf("The Session ID of child is %d.\n",getsid(0)); sleep(20); exit(0); }
父进程继续执行,操作系统对fork的实现,使这个调用在父进程中返回刚刚创建的子进程的pid(一个正整数),所以下面的if语句中pid<0, pid==0的两个分支都不会执行。所以输出i am the parent process…
子进程在之后的某个时候得到调度,它的上下文被换入,占据 CPU,操作系统对fork的实现,使得子进程中fork调用返回0。所以在这个进程(注意这不是父进程了哦,虽然是同一个程序,但是这是同一个程序的另外一次执行,在操作系统中这次执行是由另外一个进程表示的,从执行的角度说和父进程相互独立)中pid=0。这个进程继续执行的过程中,if语句中 pid<0不满足,但是pid= =0是true。所以输出i am the child process…
localhost:~$ ./sig_test process id is 463 Get a signal -SIGINT //按下Ctrl-C得到的结果 Get a signal -SIGQUIT //按下Ctrl-得到的结果 //按下Ctrl-z将进程置于后台 [1]+ Stopped ./sig_test localhost:~$ bg [1]+ ./sig_test & localhost:~$ kill -HUP 463 //向进程发送SIGHUP信号 localhost:~$ Get a signal – SIGHUP kill -9 463 //向进程发送SIGKILL信号,终止进程 localhost:~$
sigaction()
1 2
#include <signal.h> int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
#include<signal.h> #include<unistd.h> #include<stdio.h> #include<sys/time.h> int sec; voidsigroutine(int signo){ switch (signo) { case SIGALRM: printf("Catch a signal -- SIGALRM "); break; case SIGVTALRM: printf("Catch a signal -- SIGVTALRM "); break; } return; } intmain() { structitimerval value,ovalue,value2; sec = 5; printf("process id is %d ",getpid()); signal(SIGALRM, sigroutine); signal(SIGVTALRM, sigroutine); value.it_value.tv_sec = 1; value.it_value.tv_usec = 0; value.it_interval.tv_sec = 1; value.it_interval.tv_usec = 0; setitimer(ITIMER_REAL, &value, &ovalue); value2.it_value.tv_sec = 0; value2.it_value.tv_usec = 500000; value2.it_interval.tv_sec = 0; value2.it_interval.tv_usec = 500000; setitimer(ITIMER_VIRTUAL, &value2, &ovalue); for (;;) ; }
该例子的屏幕拷贝如下:
1 2 3 4 5 6 7 8
localhost:~$ ./timer_test process id is 579 Catch a signal – SIGVTALRM Catch a signal – SIGALRM Catch a signal – SIGVTALRM Catch a signal – SIGVTALRM Catch a signal – SIGALRM Catch a signal –GVTALRM
act.sa_sigaction=(void*)my_op; if(sigaction(SIGRTMIN+10,&act,NULL)) printf("install signal SIGRTMIN+10 error\n"); sigemptyset(&new_mask); sigaddset(&new_mask,SIGRTMIN+10); if(sigprocmask(SIG_BLOCK, &new_mask,&old_mask)) printf("block signal SIGRTMIN+10 error\n"); sleep(10); printf("now begin to get pending mask and unblock SIGRTMIN+10\n");
if(sigpending(&pending_mask)<0) printf("get pending mask error\n"); if(sigismember(&pending_mask,SIGRTMIN+10)) printf("signal SIGRTMIN+10 is pending\n"); if(sigprocmask(SIG_SETMASK,&old_mask,NULL)<0) printf("unblock signal error\n"); printf("signal unblocked\n"); sleep(10); }
staticvoidmy_op(int signum) { printf("receive signal %d \n",signum); }
root@linux:/mnt/hgfs/C_libary# gcc -o crtmq crtmq.c /tmp/ccZ9cTxo.o: In function `main': crtmq.c:(.text+0x31): undefined reference to `mq_open' crtmq.c:(.text+0x60): undefined reference to `mq_close' crtmq.c:(.text+0x8f): undefined reference to `mq_unlink' collect2: ld returned 1 exit status
/*消息队列属性结构体*/ structmq_attr { long mq_flags; /* Flags: 0 or O_NONBLOCK */ long mq_maxmsg; /* Max. # of messages on queue */ long mq_msgsize; /* Max. message size (bytes) */ long mq_curmsgs; /* # of messages currently in queue */ };
structmq_attr { long mq_flags; /*阻塞标志位,0为非阻塞(O_NONBLOCK)*/ long mq_maxmsg; /*队列所允许的最大消息条数*/ long mq_msgsize; /*每条消息的最大字节数*/ long mq_curmsgs; /*队列当前的消息条数*/ };
Suppose you have a long flowerbed in which some of the plots are planted and some are not. However, flowers cannot be planted in adjacent plots - they would compete for water and both would die.
Given a flowerbed (represented as an array containing 0 and 1, where 0 means empty and 1 means not empty), and a number n, return if n new flowers can be planted in it without violating the no-adjacent-flowers rule.
Example 1:
1 2
Input: flowerbed = [1,0,0,0,1], n = 1 Output: True
Example 2:
1 2
Input: flowerbed = [1,0,0,0,1], n = 2 Output: False
逐次的添加新的到数组中,然后统计最大可承受的数量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
classSolution { public: boolcanPlaceFlowers(vector<int>& flowerbed, int n){ int ans = 0; for(int i = 0; i < flowerbed.size(); i ++) { if(flowerbed[i] == 0) if(i-1 >= 0 && flowerbed[i-1] == 0 || i==0) if(i+1 < flowerbed.size() && flowerbed[i+1] == 0 || i == flowerbed.size()-1) { flowerbed[i] = 1; ans ++; } } cout << ans <<endl; return ans >= n; } };
Leetcode606. Construct String from Binary Tree
You need to construct a string consists of parenthesis and integers from a binary tree with the preorder traversing way.
The null node needs to be represented by empty parenthesis pair “()”. And you need to omit all the empty parenthesis pairs that don’t affect the one-to-one mapping relationship between the string and the original binary tree.
Example 1:
1 2 3 4 5 6 7 8 9 10 11 12
Input: Binary tree: [1,2,3,4] 1 / \ 2 3 / 4
Output: "1(2(4))(3)"
Explanation: Originallay it needs to be "1(2(4)())(3()())", but you need to omit all the unnecessary empty parenthesis pairs. And it will be "1(2(4))(3)".
Example 2:
1 2 3 4 5 6 7 8 9 10
Input: Binary tree: [1,2,3,null,4] 1 / \ 2 3 \ 4
Output: "1(2()(4))(3)" Explanation: Almost the same as the first example, except we can't omit the first parenthesis pair to break the one-to-one mapping relationship between the input and the output.
Given a list of directory info including directory path, and all the files with contents in this directory, you need to find out all the groups of duplicate files in the file system in terms of their paths.
A group of duplicate files consists of at least two files that have exactly the same content.
A single directory info string in the input list has the following format:
It means there are n files (f1.txt, f2.txt … fn.txt with content f1_content, f2_content … fn_content, respectively) in directory root/d1/d2/…/dm. Note that n >= 1 and m >= 0. If m = 0, it means the directory is just the root directory.
The output is a list of group of duplicate file paths. For each group, it contains all the file paths of the files that have the same content. A file path is a string that has the following format:
classSolution { public: vector<vector<string>> findDuplicate(vector<string>& paths) { vector<vector<string>> res; unordered_map<string, vector<string>> m; for (string path : paths) { istringstream is(path); string pre = "", t = ""; is >> pre; while (is >> t) { int idx = t.find_last_of('('); string dir = pre + "/" + t.substr(0, idx); string content = t.substr(idx + 1, t.size() - idx - 2); m[content].push_back(dir); } } for (auto a : m) { if (a.second.size() > 1)res.push_back(a.second); } return res; } };
Leetcode611. Valid Triangle Number
Given an array consists of non-negative integers, your task is to count the number of triplets chosen from the array that can make triangles if we take them as side lengths of a triangle.
Example 1:
1 2 3 4 5 6 7
Input: [2,2,3,4] Output: 3 Explanation: Valid combinations are: 2,3,4 (using the first 2) 2,3,4 (using the second 2) 2,2,3
Note:
The length of the given array won’t exceed 1000.
The integers in the given array are in the range of [0, 1000].
classSolution { public: inttriangleNumber(vector<int>& nums){ int res = 0, n = nums.size(); sort(nums.begin(), nums.end()); for (int i = 0; i < n; ++i) { for (int j = i + 1; j < n; ++j) { int sum = nums[i] + nums[j], left = j + 1, right = n; while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] < sum) left = mid + 1; else right = mid; } res += right - 1 - j; } } return res; } };
classSolution { public: inttriangleNumber(vector<int>& nums){ int res = 0, n = nums.size(); sort(nums.begin(), nums.end()); for (int i = n - 1; i >= 2; --i) { int left = 0, right = i - 1; while (left < right) { if (nums[left] + nums[right] > nums[i]) { res += right - left; --right; } else { ++left; } } } return res; } };
Leetcode617. Merge Two Binary Trees
Given two binary trees and imagine that when you put one of them to cover the other, some nodes of the two trees are overlapped while the others are not.
You need to merge them into a new binary tree. The merge rule is that if two nodes overlap, then sum node values up as the new value of the merged node. Otherwise, the NOT null node will be used as the node of new tree.
X city opened a new cinema, many people would like to go to this cinema. The cinema also gives out a poster indicating the movies’ ratings and descriptions. Please write a SQL query to output movies with an odd numbered ID and a description that is not ‘boring’. Order the result by rating.
For example, table cinema:
1 2 3 4 5 6 7 8 9
+---------+-----------+--------------+-----------+ | id | movie | description | rating | +---------+-----------+--------------+-----------+ | 1 | War | great 3D | 8.9 | | 2 | Science | fiction | 8.5 | | 3 | irish | boring | 6.2 | | 4 | Ice song | Fantacy | 8.6 | | 5 | House card| Interesting| 9.1 | +---------+-----------+--------------+-----------+
For the example above, the output should be:
1 2 3 4 5 6
+---------+-----------+--------------+-----------+ | id | movie | description | rating | +---------+-----------+--------------+-----------+ | 5 | House card| Interesting| 9.1 | | 1 | War | great 3D | 8.9 | +---------+-----------+--------------+-----------+
1
SELECT id, movie, description, rating from cinema where (id%2) !=0AND (description != "boring") ORDERBY rating DESC;
Leetcode621. Task Scheduler
Given a char array representing tasks CPU need to do. It contains capital letters A to Z where different letters represent different tasks.Tasks could be done without original order. Each task could be done in one interval. For each interval, CPU could finish one task or just be idle.
However, there is a non-negative cooling interval n that means between two same tasks, there must be at least n intervals that CPU are doing different tasks or just be idle.
You need to return the least number of intervals the CPU will take to finish all the given tasks.
Example 1:
1 2 3
Input: tasks = ['A','A','A','B','B','B'], n = 2 Output: 8 Explanation: A -> B -> idle -> A -> B -> idle -> A -> B.
classSolution { public: intleastInterval(vector<char>& tasks, int n){ vector<int> cnt(26, 0); for (char task : tasks) { ++cnt[task - 'A']; } sort(cnt.begin(), cnt.end()); int i = 25, mx = cnt[25], len = tasks.size(); while (i >= 0 && cnt[i] == mx) --i; returnmax(len, (mx - 1) * (n + 1) + 25 - i); } };
Leetcode622. Design Circular Queue
Design your implementation of the circular queue. The circular queue is a linear data structure in which the operations are performed based on FIFO (First In First Out) principle and the last position is connected back to the first position to make a circle. It is also called “Ring Buffer”.
One of the benefits of the circular queue is that we can make use of the spaces in front of the queue. In a normal queue, once the queue becomes full, we cannot insert the next element even if there is a space in front of the queue. But using the circular queue, we can use the space to store new values.
Implementation the MyCircularQueue class:
MyCircularQueue(k) Initializes the object with the size of the queue to be k.
int Front() Gets the front item from the queue. If the queue is empty, return -1.
int Rear() Gets the last item from the queue. If the queue is empty, return -1.
boolean enQueue(int value) Inserts an element into the circular queue. Return true if the operation is successful.
boolean deQueue() Deletes an element from the circular queue. Return true if the operation is successful.
boolean isEmpty() Checks whether the circular queue is empty or not.
boolean isFull() Checks whether the circular queue is full or not.
Given the root of a binary tree and two integers val and depth, add a row of nodes with value val at the given depth depth.
Note that the root node is at depth 1.
The adding rule is:
Given the integer depth, for each not null tree node cur at the depth depth - 1, create two tree nodes with value val as cur’s left subtree root and right subtree root.
cur’s original left subtree should be the left subtree of the new left subtree root.
cur’s original right subtree should be the right subtree of the new right subtree root.
If depth == 1 that means there is no depth depth - 1 at all, then create a tree node with value val as the new root of the whole original tree, and the original tree is the new root’s left subtree.
Note: If the number of students is odd, there is no need to change the last one’s seat.
交换相邻的两个学生的位置。IF语句及SELECT子句使用,如下所示:
1 2
# Write your MySQL query statement below SELECT IF(id%2=0, id-1, IF (id = (SELECTCOUNT(*) FROM seat), id, id +1)) as id , student from seat ORDERBY id;
Leetcode628. Maximum Product of Three Numbers
Given an integer array, find three numbers whose product is maximum and output the maximum product.
There are n different online courses numbered from 1 to n. Each course has some duration(course length) tand closed on dth day. A course should be taken continuously for t days and must be finished before or on the dth day. You will start at the 1st day.
Given n online courses represented by pairs (t,d), your task is to find the maximal number of courses that can be taken.
classSolution { public: staticboolcomp(vector<int>& a, vector<int>& b){ return a[1] < b[1]; } intscheduleCourse(vector<vector<int>>& courses){ int res = 0, cur = 0; priority_queue<int> q; sort(courses.begin(), courses.end(), comp); for (vector<int> a : courses) { cur += a[0]; q.push(a[0]); if (cur > a[1]) { cur -= q.top(); q.pop(); } }
return q.size(); } };
Leetcode632. Smallest Range Covering Elements from K Lists
You have k lists of sorted integers in ascending order. Find the smallest range that includes at least one number from each of the k lists.
We define the range [a,b] is smaller than range [c,d] if b-a < d-c or a < c if b-a == d-c.
Example 1:
1 2 3 4 5 6
Input:[[4,10,15,24,26], [0,9,12,20], [5,18,22,30]] Output: [20,24] Explanation: List 1: [4, 10, 15, 24,26], 24 is in range [20,24]. List 2: [0, 9, 12, 20], 20 is in range [20,24]. List 3: [5, 18, 22, 30], 22 is in range [20,24].
Note:
The given list may contain duplicates, so ascending order means >= here.
1 <= k <= 3500
-105 <= value of elements <= 105.
For Java users, please note that the input type has been changed to List. And after you reset the code template, you’ll see this point.
这道题给了我们一些数组,都是排好序的,让求一个最小的范围,使得这个范围内至少会包括每个数组中的一个数字。虽然每个数组都是有序的,但是考虑到他们之间的数字差距可能很大,所以最好还是合并成一个数组统一处理比较好,但是合并成一个大数组还需要保留其原属数组的序号,所以大数组中存pair对,同时保存数字和原数组的序号。然后重新按照数字大小进行排序,这样问题实际上就转换成了求一个最小窗口,使其能够同时包括所有数组中的至少一个数字。这不就变成了那道 Minimum Window Substring。所以说啊,这些题目都是换汤不换药的,总能变成我们见过的类型。这里用两个指针 left 和 right 来确定滑动窗口的范围,还要用一个 HashMap 来建立每个数组与其数组中数字出现的个数之间的映射,变量 cnt 表示当前窗口中的数字覆盖了几个数组,diff 为窗口的大小,让 right 向右滑动,然后判断如果 right 指向的数字所在数组没有被覆盖到,cnt 自增1,然后 HashMap 中对应的数组出现次数自增1,然后循环判断如果 cnt 此时为k(数组的个数)且 left 不大于 right,那么用当前窗口的范围来更新结果,然后此时想缩小窗口,通过将 left 向右移,移动之前需要减小 HashMap 中的映射值,因为去除了数字,如果此时映射值为0了,说明有个数组无法覆盖到了,cnt 就要自减1。这样遍历后就能得到最小的范围了,参见代码如下:
classSolution { public: vector<int> smallestRange(vector<vector<int>>& nums){ vector<int> res; vector<pair<int, int>> v; unordered_map<int, int> m; for (int i = 0; i < nums.size(); ++i) { for (int num : nums[i]) { v.push_back({num, i}); } } sort(v.begin(), v.end()); int left = 0, n = v.size(), k = nums.size(), cnt = 0, diff = INT_MAX; for (int right = 0; right < n; ++right) { if (m[v[right].second] == 0) ++cnt; ++m[v[right].second]; while (cnt == k && left <= right) { if (diff > v[right].first - v[left].first) { diff = v[right].first - v[left].first; res = {v[left].first, v[right].first}; } if (--m[v[left].second] == 0) --cnt; ++left; } } return res; } };
这道题还有一种使用 priority_queue 来做的,优先队列默认情况是最大堆,但是这道题我们需要使用最小堆,重新写一下 comparator 就行了。解题的主要思路很上面的解法很相似,只是具体的数据结构的使用上略有不同,这 curMax 表示当前遇到的最大数字,用一个 idx 数组表示每个 list 中遍历到的位置,然后优先队列里面放一个pair,是数字和其所属list组成的对儿。遍历所有的list,将每个 list 的首元素和该 list 序号组成 pair 放入队列中,然后 idx 数组中每个位置都赋值为1,因为0的位置已经放入队列了,所以指针向后移一个位置,还要更新当前最大值 curMax。此时 queue 中是每个 list 各有一个数字,由于是最小堆,所以最小的数字就在队首,再加上最大值 curMax,就可以初始化结果 res 了。然后进行循环,注意这里循环的条件不是队列不为空,而是当某个 list 的数字遍历完了就结束循环,因为范围要 cover 每个 list 至少一个数字。所以 while 循环条件即是队首数字所在的 list 的遍历位置小于该 list 的总个数,在循环中,取出队首数字所在的 list 序号t,然后将该 list 中下一个位置的数字和该 list 序号t组成 pair,加入队列中,然后用这个数字更新 curMax,同时 idx 中t对应的位置也自增1。现在来更新结果 res,如果结果 res 中两数之差大于 curMax 和队首数字之差,则更新结果 res,参见代码如下:
classSolution { public: booljudgeSquareSum(int c){ int len = sqrt(c); for(int i = 0; i <= len; i ++) { int remain = c - i*i; int rr = sqrt(remain); if(rr * rr == remain) returntrue; } returnfalse; } };
Leetcode636. Exclusive Time of Functions
On a single-threaded CPU, we execute a program containing n functions. Each function has a unique ID between 0 and n-1.
Function calls are stored in a call stack: when a function call starts, its ID is pushed onto the stack, and when a function call ends, its ID is popped off the stack. The function whose ID is at the top of the stack is the current function being executed. Each time a function starts or ends, we write a log with the ID, whether it started or ended, and the timestamp.
You are given a list logs, where logs[i] represents the ith log message formatted as a string “{function_id}:{“start” | “end”}:{timestamp}”. For example, “0:start:3” means a function call with function ID 0 started at the beginning of timestamp 3, and “1:end:2” means a function call with function ID 1 ended at the end of timestamp 2. Note that a function can be called multiple times, possibly recursively.
A function’s exclusive time is the sum of execution times for all function calls in the program. For example, if a function is called twice, one call executing for 2 time units and another call executing for 1 time unit, the exclusive time is 2 + 1 = 3.
Return the exclusive time of each function in an array, where the value at the ith index represents the exclusive time for the function with ID i.
Example 1:
1 2
Input: n = 2, logs = ["0:start:0","1:start:2","1:end:5","0:end:6"] Output: [3,4]
Explanation:
Function 0 starts at the beginning of time 0, then it executes 2 for units of time and reaches the end of time 1.
Function 1 starts at the beginning of time 2, executes for 4 units of time, and ends at the end of time 5.
Function 0 resumes execution at the beginning of time 6 and executes for 1 unit of time.
So function 0 spends 2 + 1 = 3 units of total time executing, and function 1 spends 4 units of total time executing.
Example 2:
1 2
Input: n = 1, logs = ["0:start:0","0:start:2","0:end:5","0:start:6","0:end:6","0:end:7"] Output: [8]
Explanation:
Function 0 starts at the beginning of time 0, executes for 2 units of time, and recursively calls itself.
Function 0 (recursive call) starts at the beginning of time 2 and executes for 4 units of time.
Function 0 (initial call) resumes execution then immediately calls itself again.
Function 0 (2nd recursive call) starts at the beginning of time 6 and executes for 1 unit of time.
Function 0 (initial call) resumes execution at the beginning of time 7 and executes for 1 unit of time.
So function 0 spends 2 + 4 + 1 + 1 = 8 units of total time executing.
Example 3:
1 2
Input: n = 2, logs = ["0:start:0","0:start:2","0:end:5","1:start:6","1:end:6","0:end:7"] Output: [7,1]
Explanation:
Function 0 starts at the beginning of time 0, executes for 2 units of time, and recursively calls itself.
Function 0 (recursive call) starts at the beginning of time 2 and executes for 4 units of time.
Function 0 (initial call) resumes execution then immediately calls function 1.
Function 1 starts at the beginning of time 6, executes 1 units of time, and ends at the end of time 6.
Function 0 resumes execution at the beginning of time 6 and executes for 2 units of time.
So function 0 spends 2 + 4 + 1 = 7 units of total time executing, and function 1 spends 1 unit of total time executing.
Example 4:
1 2
Input: n = 2, logs = ["0:start:0","0:start:2","0:end:5","1:start:7","1:end:7","0:end:8"] Output: [8,1]
Example 5:
1 2
Input: n = 1, logs = ["0:start:0","0:end:0"] Output: [1]
classSolution { public: vector<int> exclusiveTime(int n, vector<string>& logs){ vector<int> res(n, 0); int cur = 0, pre = 0; stack<int> ss; for (string s : logs) { int len = s.length(); int p = 0, id = 0, time = 0, type; while(p < len) { while(s[p] != ':') id = id * 10 + s[p++] - '0'; p ++; if (s[p] == 's') type = 1; else type = 0; while(p < len && s[p++] != ':'); while(p < len) time = time * 10 + s[p++] - '0'; if (!ss.empty()) { res[ss.top()] += (time - pre); } pre = time; if (type == 1) { ss.push(id); } else { int i = ss.top(); ss.pop(); res[i] ++; pre ++; } } } return res; } };
Leetcode637. Average of Levels in Binary Tree
Given a non-empty binary tree, return the average value of the nodes on each level in the form of an array.
Example 1:
1 2 3 4 5 6 7 8 9
Input: 3 / \ 9 20 / \ 15 7 Output: [3, 14.5, 11] Explanation: The average value of nodes on level 0 is 3, on level 1 is 14.5, and on level 2 is 11. Hence return [3, 14.5, 11].
classSolution { public: vector<double> averageOfLevels(TreeNode* root){ queue<TreeNode*> q; vector<double> res; q.push(root); int counter; while(!q.empty()) { vector<TreeNode*> vec; double sum = 0; int n = q.size(); while(n--) { TreeNode* temp = q.front(); sum += temp->val; vec.push_back(temp); q.pop(); if(temp->left) q.push(temp->left); if(temp->right) q.push(temp->right); } sum = (double)sum / vec.size(); res.push_back(sum); } return res; } };
Leetcode638. Shopping Offers
In LeetCode Store, there are some kinds of items to sell. Each item has a price.
However, there are some special offers, and a special offer consists of one or more different kinds of items with a sale price.
You are given the each item’s price, a set of special offers, and the number we need to buy for each item. The job is to output the lowest price you have to pay for exactly certain items as given, where you could make optimal use of the special offers.
Each special offer is represented in the form of an array, the last number represents the price you need to pay for this special offer, other numbers represents how many specific items you could get if you buy this offer.
You could use any of special offers as many times as you want.
Example 1:
1 2 3 4 5 6 7
Input: [2,5], [[3,0,5],[1,2,10]], [3,2] Output: 14 Explanation: There are two kinds of items, A and B. Their prices are $2 and $5 respectively. In special offer 1, you can pay $5 for 3A and 0B In special offer 2, you can pay $10 for 1A and 2B. You need to buy 3A and 2B, so you may pay $10 for 1A and 2B (special offer #2), and $4 for 2A.
Example 2:
1 2 3 4 5 6 7
Input: [2,3,4], [[1,1,0,4],[2,2,1,9]], [1,2,1] Output: 11 Explanation: The price of A is $2, and $3 for B, $4 for C. You may pay $4 for 1A and 1B, and $9 for 2A ,2B and 1C. You need to buy 1A ,2B and 1C, so you may pay $4 for 1A and 1B (special offer #1), and $3 for 1B, $4 for 1C. You cannot add more items, though only $9 for 2A ,2B and 1C.
Note:
There are at most 6 kinds of items, 100 special offers.
For each item, you need to buy at most 6 of them.
You are not allowed to buy more items than you want, even if that would lower the overall price.
classSolution { public: intshoppingOffers(vector<int>& price, vector<vector<int>>& specials, vector<int>& needs){ int len = price.size(); int res = 0; for (int i = 0; i < len; i ++) res += (price[i] * needs[i]); for (auto special : specials) { bool isvalid = true; for (int i = 0; i < len; i ++) { if (needs[i] < special[i]) isvalid = false; needs[i] -= special[i]; } if (isvalid) res = min(res, shoppingOffers(price, specials, needs) + special.back()); for (int i = 0; i < len; i ++) { needs[i] += special[i]; } } return res; } };
Leetcode639. Decode Ways II
A message containing letters from A-Z is being encoded to numbers using the following mapping way:
1 2 3 4
'A' -> 1 'B' -> 2 ... 'Z' -> 26
Beyond that, now the encoded string can also contain the character ‘*’, which can be treated as one of the numbers from 1 to 9.
Given the encoded message containing digits and the character ‘*’, return the total number of ways to decode it.
Also, since the answer may be very large, you should return the output mod 109 + 7.
Example 1:
1 2 3
Input: "*" Output: 9 Explanation: The encoded message can be decoded to the string: "A", "B", "C", "D", "E", "F", "G", "H", "I".
Example 2:
1 2
Input: "1*" Output: 9 + 9 = 18
Note:
The length of the input string will fit in range [1, 105].
The input string will only contain the character ‘*’ and digits ‘0’ - ‘9’.
Solve a given equation and return the value of ‘x’ in the form of a string “x=#value”. The equation contains only ‘+’, ‘-‘ operation, the variable ‘x’ and its coefficient. You should return “No solution” if there is no solution for the equation, or “Infinite solutions” if there are infinite solutions for the equation.
If there is exactly one solution for the equation, we ensure that the value of ‘x’ is an integer.
classSolution { public: voidhelper(string e, int &i, int &x_l, int &l, bool isright){ int len = e.length(); int num, flag; if (isright) i ++; while(i < len && (isright || e[i] != '=')) { num = 0; flag = 1; if (e[i] == '-') { flag = -1; i++; } elseif (e[i] == '+') { flag = 1; i ++; } elseif (e[i] == 'x') { x_l ++; i ++; } int or_i = i; while(i < len && '0' <= e[i] && e[i] <= '9') num = num * 10 + e[i++] - '0'; num *= flag; if (num == 0 && flag == -1) num = -1; if (num != 0 && e[i] == 'x' || or_i != i && e[i] == 'x') { x_l += num; i ++; } else { l += num; } } } string solveEquation(string e){ int len = e.length(), i = 0; int x_l = 0, x_r = 0; int l = 0, r = 0; int num, flag;
helper(e, i, x_l, l, false); helper(e, i, x_r, r, true); if (l - r == 0 && x_l - x_r == 0) return"Infinite solutions"; elseif (x_l - x_r == 0 && l - r != 0) return"No solution"; elseif (l - r == 0 && x_l - x_r != 0) return"x=0"; else { x_l -= x_r; r -= l; return"x="+to_string(r/x_l); } return""; } };
Leetcode641. Design Circular Deque
Design your implementation of the circular double-ended queue (deque).
Your implementation should support following operations:
MyCircularDeque(k): Constructor, set the size of the deque to be k.
insertFront(): Adds an item at the front of Deque. Return true if the operation is successful.
insertLast(): Adds an item at the rear of Deque. Return true if the operation is successful.
deleteFront(): Deletes an item from the front of Deque. Return true if the operation is successful.
deleteLast(): Deletes an item from the rear of Deque. Return true if the operation is successful.
getFront(): Gets the front item from the Deque. If the deque is empty, return -1.
getRear(): Gets the last item from Deque. If the deque is empty, return -1.
isEmpty(): Checks whether Deque is empty or not.
isFull(): Checks whether Deque is full or not.
Example:
1 2 3 4 5 6 7 8 9 10
MyCircularDeque circularDeque = newMycircularDeque(3); // set the size to be 3 circularDeque.insertLast(1); // return true circularDeque.insertLast(2); // return true circularDeque.insertFront(3); // return true circularDeque.insertFront(4); // return false, the queue is full circularDeque.getRear(); // return 2 circularDeque.isFull(); // return true circularDeque.deleteLast(); // return true circularDeque.insertFront(4); // return true circularDeque.getFront(); // return 4
Note:
All values will be in the range of [0, 1000].
The number of operations will be in the range of [1, 1000].
classMyCircularDeque { public: vector<int> v; int size, head, tail, cnt; /** Initialize your data structure here. Set the size of the deque to be k. */ MyCircularDeque(int k) { size = k; head = k-1; tail = 0; cnt = 0; v.resize(k); } /** Adds an item at the front of Deque. Return true if the operation is successful. */ boolinsertFront(int value){ if (isFull()) returnfalse; v[head] = value; head = (head-1+size) % size; cnt ++; returntrue; } /** Adds an item at the rear of Deque. Return true if the operation is successful. */ boolinsertLast(int value){ if (isFull()) returnfalse; v[tail] = value; tail = (tail + 1) % size; cnt ++; returntrue; } /** Deletes an item from the front of Deque. Return true if the operation is successful. */ booldeleteFront(){ if (isEmpty()) returnfalse; head = (head + 1) % size; cnt --; returntrue; } /** Deletes an item from the rear of Deque. Return true if the operation is successful. */ booldeleteLast(){ if (isEmpty()) returnfalse; tail = (tail - 1 + size) % size; cnt --; returntrue; } /** Get the front item from the deque. */ intgetFront(){ if (isEmpty()) return-1; return v[(head+1)%size]; } /** Get the last item from the deque. */ intgetRear(){ if (isEmpty()) return-1; return v[(tail-1+size)%size]; } /** Checks whether the circular deque is empty or not. */ boolisEmpty(){ return cnt == 0; } /** Checks whether the circular deque is full or not. */ boolisFull(){ return cnt == size; } };
Leetcode643. Maximum Average Subarray I
Given an array consisting of n integers, find the contiguous subarray of given length k that has the maximum average value. And you need to output the maximum average value.
Example 1:
1 2 3
Input: [1,12,-5,-6,50,3], k = 4 Output: 12.75 Explanation: Maximum average is (12-5-6+50)/4 = 51/4 = 12.75
Note:
1 <= k <= n <= 30,000.
Elements of the given array will be in the range [-10,000, 10,000].
1 2 3 4 5 6 7 8 9 10 11 12 13 14
classSolution { public: doublefindMaxAverage(vector<int>& nums, int k){ double sum = 0.0; for(int i = 0; i < k; i ++) sum += nums[i]; double res = sum; for(int i = k; i < nums.size(); i ++) { sum = sum + nums[i] - nums[i-k]; res = max(res, sum); } return res / k; } };
Leetcode645. Set Mismatch
The set S originally contains numbers from 1 to n. But unfortunately, due to the data error, one of the numbers in the set got duplicated to another number in the set, which results in repetition of one number and loss of another number.
Given an array nums representing the data status of this set after the error. Your task is to firstly find the number occurs twice and then find the number that is missing. Return them in the form of an array.
这道题给了一个字符串,让我们计算有多少个回文子字符串。以字符串中的每一个字符都当作回文串中间的位置,然后向两边扩散,每当成功匹配两个左右两个字符,结果 res 自增1,然后再比较下一对。注意回文字符串有奇数和偶数两种形式,如果是奇数长度,那么i位置就是中间那个字符的位置,所以左右两遍都从i开始遍历;如果是偶数长度的,那么i是最中间两个字符的左边那个,右边那个就是 i+1,这样就能 cover 所有的情况啦,而且都是不同的回文子字符串,参见代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
classSolution { public: intcountSubstrings(string s){ if (s.empty()) return0; int n = s.size(), res = 0; for (int i = 0; i < n; ++i) { helper(s, i, i, res); helper(s, i, i + 1, res); } return res; } voidhelper(string s, int i, int j, int& res){ while (i >= 0 && j < s.size() && s[i] == s[j]) { --i; ++j; ++res; } } };
classSolution { public: intcountSubstrings(string s){ int n = s.size(), res = 0; vector<vector<bool>> dp(n, vector<bool>(n)); for (int i = n - 1; i >= 0; --i) { for (int j = i; j < n; ++j) { dp[i][j] = (s[i] == s[j]) && (j - i <= 2 || dp[i + 1][j - 1]); if (dp[i][j]) ++res; } } return res; } };
Leetcode648. Replace Words
In English, we have a concept called root, which can be followed by some other words to form another longer word - let’s call this word successor. For example, the root an, followed by other, which can form another word another.
Now, given a dictionary consisting of many roots and a sentence. You need to replace all the successor in the sentence with the root forming it. If a successor has many roots can form it, replace it with the root with the shortest length.
You need to output the sentence after the replacement.
Example 1:
1 2
Input: dictionary = ["cat","bat","rat"], sentence = "the cattle was rattled by the battery" Output: "the cat was rat by the bat"
Example 2:
1 2
Input: dictionary = ["a","b","c"], sentence = "aadsfasf absbs bbab cadsfafs" Output: "a a b c"
Example 3:
1 2
Input: dictionary = ["a", "aa", "aaa", "aaaa"], sentence = "a aa a aaaa aaa aaa aaa aaaaaa bbb baba ababa" Output: "a a a a a a a a bbb baba a"
Example 4:
1 2
Input: dictionary = ["catt","cat","bat","rat"], sentence = "the cattle was rattled by the battery" Output: "the cat was rat by the bat"
Example 5:
1 2
Input: dictionary = ["ac","ab"], sentence = "it is abnormal that this solution is accepted" Output: "it is ab that this solution is ac"
classSolution { public: classTrie { public: bool isword; Trie* t[26]; Trie() : isword(false) { for(int i = 0; i < 26; i ++) t[i] = NULL; } }; string replaceWords(vector<string>& dictionary, string sentence){ string res = ""; Trie *root = newTrie(); for (int i = 0; i < dictionary.size(); i ++) insert(root, dictionary[i]); int i = 0, len = sentence.length(); while (i < len) { res += find(root, sentence, i); while(i < len && sentence[i] != ' ') i ++; i ++; if (i < len) res += ' '; } return res; } voidinsert(Trie *root, string s){ for (char c : s) { if (root->t[c - 'a'] == NULL) { root->t[c - 'a'] = newTrie(); } root = root->t[c - 'a']; } root->isword = true; } string find(Trie *root, string s, int& i){ int len = s.length(); string t = ""; while(i < len && s[i] != ' ') { root = root->t[s[i] - 'a']; if (root == NULL) break; t += s[i ++]; if (root->isword) return t; } while (i < len && s[i] != ' ') { t += s[i++]; } return t; } };
Leetcode649. Dota2 Senate
In the world of Dota2, there are two parties: the Radiant and the Dire.
The Dota2 senate consists of senators coming from two parties. Now the senate wants to make a decision about a change in the Dota2 game. The voting for this change is a round-based procedure. In each round, each senator can exercise one of the two rights:
Ban one senator’s right: A senator can make another senator lose all his rights in this and all the following rounds.
Announce the victory: If this senator found the senators who still have rights to vote are all from the same party, he can announce the victory and make the decision about the change in the game.
Given a string representing each senator’s party belonging. The character ‘R’ and ‘D’ represent the Radiant party and the Dire party respectively. Then if there are n senators, the size of the given string will be n.
The round-based procedure starts from the first senator to the last senator in the given order. This procedure will last until the end of voting. All the senators who have lost their rights will be skipped during the procedure.
Suppose every senator is smart enough and will play the best strategy for his own party, you need to predict which party will finally announce the victory and make the change in the Dota2 game. The output should be Radiant or Dire.
Example 1:
1 2 3 4 5
Input: "RD" Output: "Radiant" Explanation: The first senator comes from Radiant and he can just ban the next senator's right in the round 1. And the second senator can't exercise any rights any more since his right has been banned. And in the round 2, the first senator can just announce the victory since he is the only guy in the senate who can vote.
Example 2:
1 2 3 4 5 6 7
Input: "RDD" Output: "Dire" Explanation: The first senator comes from Radiant and he can just ban the next senator's right in the round 1. And the second senator can't exercise any rights anymore since his right has been banned. And the third senator comes from Dire and he can ban the first senator's right in the round 1. And in the round 2, the third senator can just announce the victory since he is the only guy in the senate who can vote.
classSolution { public: string predictPartyVictory(string senate){ int n = senate.size(); queue<int> q1, q2; for (int i = 0; i < n; ++i) { (senate[i] == 'R') ? q1.push(i) : q2.push(i); } while (!q1.empty() && !q2.empty()) { int i = q1.front(); q1.pop(); int j = q2.front(); q2.pop(); (i < j) ? q1.push(i + n) : q2.push(j + n); } return (q1.size() > q2.size()) ? "Radiant" : "Dire"; } };
Leetcode650. 2 Keys Keyboard
Initially on a notepad only one character ‘A’ is present. You can perform two operations on this notepad for each step:
Copy All: You can copy all the characters present on the notepad (partial copy is not allowed).
Paste: You can paste the characters which are copied last time.
Given a number n. You have to get exactly n ‘A’ on the notepad by performing the minimum number of steps permitted. Output the minimum number of steps to get n ‘A’.
Example 1:
1 2 3 4 5 6 7
Input: 3 Output: 3 Explanation: Intitally, we have one character 'A'. In step 1, we use Copy All operation. In step 2, we use Paste operation to get 'AA'. In step 3, we use Paste operation to get 'AAA'.
通过分析上面这6个简单的例子,已经可以总结出一些规律了,首先对于任意一个n(除了1以外),最差的情况就是用n步,不会再多于n步,但是有可能是会小于n步的,比如 n=6 时,就只用了5步,仔细分析一下,发现时先拼成了 AAA,再复制粘贴成了 AAAAAA。那么什么情况下可以利用这种方法来减少步骤呢,分析发现,小模块的长度必须要能整除n,这样才能拆分。对于 n=6,我们其实还可先拼出 AA,然后再复制一次,粘贴两次,得到的还是5。分析到这里,解题的思路应该比较清晰了,找出n的所有因子,然后这个因子可以当作模块的个数,再算出模块的长度 n/i,调用递归,加上模块的个数i来更新结果 res 即可,参见代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
classSolution { public: intminSteps(int n){ if (n == 1) return0; int res = n; for (int i = n - 1; i > 1; --i) { if (n % i == 0) { res = min(res, minSteps(n / i) + i); } } return res; } };
Given an integer array with no duplicates. A maximum tree building on this array is defined as follow:
The root is the maximum number in the array. The left subtree is the maximum tree constructed from left part subarray divided by the maximum number. The right subtree is the maximum tree constructed from right part subarray divided by the maximum number. Construct the maximum tree by the given array and output the root node of this tree.
Example 1:
1 2 3 4 5 6 7 8 9 10
Input: [3,2,1,6,0,5] Output: return the tree root node representing the following tree:
6 / \ 3 5 \ / 2 0 \ 1
Note: The size of the given array will be in the range [1,1000].
Print a binary tree in an m*n 2D string array following these rules:
The row number m should be equal to the height of the given binary tree.
The column number n should always be an odd number.
The root node’s value (in string format) should be put in the exactly middle of the first row it can be put. The column and the row where the root node belongs will separate the rest space into two parts (left-bottom part and right-bottom part). You should print the left subtree in the left-bottom part and print the right subtree in the right-bottom part. The left-bottom part and the right-bottom part should have the same size. Even if one subtree is none while the other is not, you don’t need to print anything for the none subtree but still need to leave the space as large as that for the other subtree. However, if two subtrees are none, then you don’t need to leave space for both of them.
Each unused space should contain an empty string “”.
classSolution { public: vector<vector<string>> printTree(TreeNode* root) { int h = getHeight(root), w = pow(2, h) - 1; vector<vector<string>> res(h, vector<string>(w, "")); helper(root, 0, w - 1, 0, h, res); return res; } voidhelper(TreeNode* node, int i, int j, int curH, int height, vector<vector<string>>& res){ if (!node || curH == height) return; res[curH][(i + j) / 2] = to_string(node->val); helper(node->left, i, (i + j) / 2, curH + 1, height, res); helper(node->right, (i + j) / 2 + 1, j, curH + 1, height, res); } intgetHeight(TreeNode* node){ if (!node) return0; return1 + max(getHeight(node->left), getHeight(node->right)); } };
Leetcode657. Robot Return to Origin
There is a robot starting at position (0, 0), the origin, on a 2D plane. Given a sequence of its moves, judge if this robot ends up at (0, 0) after it completes its moves.
The move sequence is represented by a string, and the character moves[i] represents its ith move. Valid moves are R (right), L (left), U (up), and D (down). If the robot returns to the origin after it finishes all of its moves, return true. Otherwise, return false.
Note: The way that the robot is “facing” is irrelevant. “R” will always make the robot move to the right once, “L” will always make it move left, etc. Also, assume that the magnitude of the robot’s movement is the same for each move.
Example 1:
1 2 3
Input: "UD" Output: true Explanation: The robot moves up once, and then down once. All moves have the same magnitude, so it ended up at the origin where it started. Therefore, we return true.
Example 2:
1 2 3
Input: "LL" Output: false Explanation: The robot moves left twice. It ends up two "moves" to the left of the origin. We return false because it is not at the origin at the end of its moves.
Given a sorted array, two integers k and x, find the k closest elements to x in the array. The result should also be sorted in ascending order. If there is a tie, the smaller elements are always preferred.
Example 1:
1 2
Input: [1,2,3,4,5], k=4, x=3 Output: [1,2,3,4]
Example 2:
1 2
Input: [1,2,3,4,5], k=4, x=-1 Output: [1,2,3,4]
Note:
The value k is positive and will always be smaller than the length of the sorted array.
Length of the given array is positive and will not exceed 104
Absolute value of elements in the array and x will not exceed 104
classSolution { public: vector<int> findClosestElements(vector<int>& arr, int k, int x){ int len = arr.size(), l = 0, r = len-1; while(r - l + 1 > k) { if (x - arr[l] < arr[r] - x) r --; elseif (x - arr[l] > arr[r] - x) l ++; else { if (arr[l] < arr[r]) r --; else l ++; } } vector<int> res; for (int i = l; i <= r; i ++) res.push_back(arr[i]); return res; } };
下面这种解法是论坛上的高分解法,用到了二分搜索法。其实博主最开始用的方法并不是帖子中的这两个方法,虽然也是用的二分搜索法,但博主搜的是第一个不小于x的数,然后同时向左右两个方向遍历,每次取和x距离最小的数加入结果 res 中,直到取满k个为止。但是下面这种方法更加巧妙一些,二分法的判定条件做了一些改变,就可以直接找到要返回的k的数字的子数组的起始位置,感觉非常的神奇。每次比较的是 mid 位置和x的距离跟 mid+k 跟x的距离,以这两者的大小关系来确定二分法折半的方向,最后找到最近距离子数组的起始位置,参见代码如下:
1 2 3 4 5 6 7 8 9 10 11 12
classSolution { public: vector<int> findClosestElements(vector<int>& arr, int k, int x){ int left = 0, right = arr.size() - k; while (left < right) { int mid = left + (right - left) / 2; if (x - arr[mid] > arr[mid + k] - x) left = mid + 1; else right = mid; } returnvector<int>(arr.begin() + left, arr.begin() + left + k); } };
Leetcode659. Split Array into Consecutive Subsequences
You are given an integer array sorted in ascending order (may contain duplicates), you need to split them into several subsequences, where each subsequences consist of at least 3 consecutive integers. Return whether you can make such a split.
Example 1:
1 2 3 4 5 6
Input: [1,2,3,3,4,5] Output: True Explanation: You can split them into two consecutive subsequences : 1, 2, 3 3, 4, 5
Example 2:
1 2 3 4 5 6
Input: [1,2,3,3,4,4,5,5] Output: True Explanation: You can split them into two consecutive subsequences : 1, 2, 3, 4, 5 3, 4, 5
Example 3:
1 2
Input: [1,2,3,4,4,5] Output: False
这道题让将数组分割成多个连续递增的子序列,注意这里可能会产生歧义,实际上应该是分割成一个或多个连续递增的子序列,因为 [1,2,3,4,5] 也是正确的解。这道题就用贪婪解法就可以了,使用两个 HashMap,第一个 HashMap 用来建立数字和其出现次数之间的映射 freq,第二个用来建立可以加在某个连续子序列后的数字与其可以出现的次数之间的映射 need。对于第二个 HashMap,举个例子来说,就是假如有个连牌,比如对于数字1,此时检测数字2和3是否存在,若存在的话,表明有连牌 [1,2,3] 存在,由于后面可以加上4,组成更长的连牌,所以不管此时牌里有没有4,都可以建立 4->1 的映射,表明此时需要一个4。这样首先遍历一遍数组,统计每个数字出现的频率,然后开始遍历数组,对于每个遍历到的数字,首先看其当前出现的次数,如果为0,则继续循环;如果 need 中存在这个数字的非0映射,那么表示当前的数字可以加到某个连的末尾,将当前数字在 need 中的映射值自减1,然后将下一个连续数字的映射值加1,因为当 [1,2,3] 连上4后变成 [1,2,3,4] 之后,就可以连上5了,说明此时还需要一个5;如果不能连到其他子序列后面,则来看其是否可以成为新的子序列的起点,可以通过看后面两个数字的映射值是否大于0,都大于0的话,说明可以组成3连儿,于是将后面两个数字的映射值都自减1,还有由于组成了3连儿,在 need 中将末尾的下一位数字的映射值自增1;如果上面情况都不满足,说明该数字是单牌,只能划单儿,直接返回 false。最后别忘了将当前数字的 freq 映射值自减1。退出 for 循环后返回 true,参见代码如下:
Given a 2D integer matrix M representing the gray scale of an image, you need to design a smoother to make the gray scale of each cell becomes the average gray scale (rounding down) of all the 8 surrounding cells and itself. If a cell has less than 8 surrounding cells, then use as many as you can.
Example 1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Input: [[1,1,1], [1,0,1], [1,1,1]] Output: [[0, 0, 0], [0, 0, 0], [0, 0, 0]] Explanation: For the point (0,0), (0,2), (2,0), (2,2): floor(3/4) = floor(0.75) = 0 For the point (0,1), (1,0), (1,2), (2,1): floor(5/6) = floor(0.83333333) = 0 For the point (1,1): floor(8/9) = floor(0.88888889) = 0 Note: The value in the given matrix is in the range of [0, 255]. The length and width of the given matrix are in the range of [1, 150].
classSolution { public: intgetval(vector<vector<int>>& M, int ii, int jj){ int sum = 0; int count = 0; for(int i = ii-1; i <= ii+1; i ++) for(int j = jj-1; j <= jj+1; j ++) if(i >= 0 && j >= 0 && i < M.size() && j < M[0].size()) { count ++; sum += M[i][j]; } return sum / count; } vector<vector<int>> imageSmoother(vector<vector<int>>& M) { int m = M.size(), n = M[0].size(); vector<vector<int>> res(m, vector(n, 0)); for(int i = 0; i < m; i ++) for(int j = 0; j < n; j ++) res[i][j] = getval(M, i, j); return res; } };
Leetcode662. Maximum Width of Binary Tree
Given a binary tree, write a function to get the maximum width of the given tree. The width of a tree is the maximum width among all levels. The binary tree has the same structure as a full binary tree, but some nodes are null.
The width of one level is defined as the length between the end-nodes (the leftmost and right most non-null nodes in the level, where the null nodes between the end-nodes are also counted into the length calculation.
Example 1:
1 2 3 4 5 6 7 8 9
Input: 1 / \ 3 2 / \ \ 5 3 9
Output: 4 Explanation: The maximum width existing in the third level with the length 4 (5,3,null,9).
Example 2:
1 2 3 4 5 6 7 8
Input: 1 / 3 / \ 5 3 Output: 2 Explanation: The maximum width existing in the third level with the length 2 (5,3).
Example 3:
1 2 3 4 5 6 7 8 9
Input: 1 / \ 3 2 / 5
Output: 2 Explanation: The maximum width existing in the second level with the length 2 (3,2).
Example 4:
1 2 3 4 5 6 7 8 9 10 11
Input:
1 / \ 3 2 / \ 5 9 / \ 6 7 Output: 8 Explanation:The maximum width existing in the fourth level with the length 8 (6,null,null,null,null,null,null,7).
classSolution { public: intwidthOfBinaryTree(TreeNode* root){ if (!root) return0; queue<pair<TreeNode*, int>> q; q.push({root, 1}); int res = 0; while(!q.empty()) { int cnt = q.size(); int left = q.front().second, right = left; int maxx = q.back().second; for (int i = 0; i < cnt; i ++) { TreeNode* tmp = q.front().first; right = q.front().second; q.pop(); if (tmp->left) q.push({tmp->left, right*2-maxx}); if (tmp->right) q.push({tmp->right, right*2+1-maxx}); } res = max(res, right - left + 1); } return res; } };
Leetcode664. Strange Printer
There is a strange printer with the following two special requirements:
The printer can only print a sequence of the same character each time. At each turn, the printer can print new characters starting from and ending at any places, and will cover the original existing characters.
Given a string consists of lower English letters only, your job is to count the minimum number of turns the printer needed in order to print it.
Example 1:
1 2 3
Input: "aaabbb" Output: 2 Explanation: Print "aaa" first and then print "bbb".
Example 2:
1 2 3
Input: "aba" Output: 2 Explanation: Print "aaa" first and then print "b" from the second place of the string, which will cover the existing character 'a'.
classSolution { public: boolcheckPossibility(vector<int>& nums){ bool res = true; int count = 1; for(int i = 1; i < nums.size(); i ++) if(nums[i-1] > nums[i]) { if(!count) { returnfalse; } if (i == 1 || nums[i] >= nums[i - 2]) nums[i-1] = nums[i]; else nums[i] = nums[i-1]; count --; } returntrue; } };
Leetcode667. Beautiful Arrangement II
Given two integers n and k, you need to construct a list which contains n different positive integers ranging from 1 to n and obeys the following requirement:
Suppose this list is [a1, a2, a3, … , an], then the list [|a1 - a2|, |a2 - a3|, |a3 - a4|, … , |an-1 - an|] has exactly k distinct integers.
If there are multiple answers, print any of them.
Example 1:
1 2 3
Input: n = 3, k = 1 Output: [1, 2, 3] Explanation: The [1, 2, 3] has three different positive integers ranging from 1 to 3, and the [1, 1] has exactly 1 distinct integer: 1.
Example 2:
1 2 3
Input: n = 3, k = 2 Output: [1, 3, 2] Explanation: The [1, 3, 2] has three different positive integers ranging from 1 to 3, and the [2, 1] has exactly 2 distinct integers: 1 and 2.
classSolution { public: vector<int> constructArray(int n, int k){ vector<int> res; int i = 1, j = n; while (i <= j) { if (k > 1) res.push_back(k-- % 2 ? i++ : j--); else res.push_back(i++); } return res; } };
Leetcode669. Trim a Binary Search Tree
Given a binary search tree and the lowest and highest boundaries as L and R, trim the tree so that all its elements lies in [L, R] (R >= L). You might need to change the root of the tree, so the result should return the new root of the trimmed binary search tree.
classSolution { public: intmaximumSwap(int num){ vector<int> r; int o_num = num; while(num > 0) { r.push_back(num%10); num /= 10; } int len = r.size(), i, j; vector<int> max_num(len, -1); for (i = len-1; i >= 0; i --) { for (j = i; j >= 0; j --) { max_num[i] = max(max_num[i], r[j]); } } for (i = len-1; i >= 0; i --) { if (max_num[i] > r[i]) break; } if (i == -1) return o_num; for (j = 0; j < len; j ++ ) if (r[j] == max_num[i]) break; swap(r[i], r[j]); int res = 0; for (i = len-1; i >= 0; i --) res = res * 10 + r[i]; return res; } };
Leetcode671. Second Minimum Node In a Binary Tree
Given a non-empty special binary tree consisting of nodes with the non-negative value, where each node in this tree has exactly two or zero sub-node. If the node has two sub-nodes, then this node’s value is the smaller value among its two sub-nodes. More formally, the property root.val = min(root.left.val, root.right.val) always holds.
Given such a binary tree, you need to output the second minimum value in the set made of all the nodes’ value in the whole tree.
If no such second minimum value exists, output -1 instead.
Example 1:
1 2 3 4 5 6 7 8 9
Input: 2 / \ 2 5 / \ 5 7
Output: 5 Explanation: The smallest value is 2, the second smallest value is 5.
Example 2:
1 2 3 4 5 6 7
Input: 2 / \ 2 2
Output: -1 Explanation: The smallest value is 2, but there isn't any second smallest value.
There is a room with n lights which are turned on initially and 4 buttons on the wall. After performing exactly m unknown operations towards buttons, you need to return how many different kinds of status of the n lights could be.
Suppose n lights are labeled as number [1, 2, 3 …, n], function of these 4 buttons are given below:
Flip all the lights.
Flip lights with even numbers.
Flip lights with odd numbers.
Flip lights with (3k + 1) numbers, k = 0, 1, 2, …
Example 1:
1 2 3
Input: n = 1, m = 1. Output: 2 Explanation: Status can be: [on], [off]
Example 2:
1 2 3
Input: n = 2, m = 1. Output: 3 Explanation: Status can be: [on, off], [off, on], [off, off]
Example 3:
1 2 3
Input: n = 3, m = 1. Output: 4 Explanation: Status can be: [off, on, off], [on, off, on], [off, off, off], [off, on, on].
classSolution { public: intflipLights(int n, int m){ n = (n <= 6) ? n : (n % 6 + 6); int start = (1 << n) - 1; unordered_set<int> s; queue<int> q{{start}}; for (int i = 0; i < m; ++i) { int len = q.size(); s.clear(); for (int k = 0; k < len; ++k) { int t = q.front(); q.pop(); vector<int> next{flipAll(t, n), flipEven(t, n), flipOdd(t, n), flip3k1(t, n)}; for (int num : next) { if (s.count(num)) continue; q.push(num); s.insert(num); } } } return q.size(); } intflipAll(int t, int n){ int x = (1 << n) - 1; return t ^ x; } intflipEven(int t, int n){ for (int i = 0; i < n; i += 2) { t ^= (1 << i); } return t; } intflipOdd(int t, int n){ for (int i = 1; i < n; i += 2) { t ^= (1 << i); } return t; } intflip3k1(int t, int n){ for (int i = 0; i < n; i += 3) { t ^= (1 << i); } return t; } };
classSolution { public: intflipLights(int n, int m){ if (n == 0 || m == 0) return1; if (n == 1) return2; if (n == 2) return m == 1 ? 3 : 4; if (m == 1) return4; return m == 2 ? 7 : 8; } };
Leetcode673. Number of Longest Increasing Subsequence
Given an unsorted array of integers, find the number of longest increasing subsequence.
Example 1:
1 2 3
Input: [1,3,5,4,7] Output: 2 Explanation: The two longest increasing subsequence are [1, 3, 4, 7] and [1, 3, 5, 7].
Example 2:
1 2 3
Input: [2,2,2,2,2] Output: 5 Explanation: The length of longest continuous increasing subsequence is 1, and there are 5 subsequences' length is 1, so output 5.
Note: Length of the given array will be not exceed 2000 and the answer is guaranteed to be fit in 32-bit signed int.
这道题给了我们一个数组,让求最长递增序列的个数。这里用len[i]表示以nums[i]为结尾的递推序列的长度,用cnt[i]表示以nums[i]为结尾的递推序列的个数,初始化都赋值为1,只要有数字,那么至少都是1。然后遍历数组,对于每个遍历到的数字nums[i],再遍历其之前的所有数字nums[j],当nums[i]小于等于nums[j]时,不做任何处理,因为不是递增序列。反之,则判断len[i]和len[j]的关系,如果len[i]等于len[j] + 1,说明nums[i]这个数字可以加在以nums[j]结尾的递增序列后面,并且以nums[j]结尾的递增序列个数可以直接加到以nums[i]结尾的递增序列个数上。如果len[i]小于len[j] + 1,说明找到了一条长度更长的递增序列,那么此时将len[i]更新为len[j]+1,并且原本的递增序列都不能用了,直接用cnt[j]来代替。在更新完len[i]和cnt[i]之后,要更新 mx 和结果 res,如果 mx 等于 len[i],则把cnt[i]加到结果 res 之上;如果 mx 小于len[i],则更新 mx 为len[i],更新结果 res 为cnt[i],参见代码如下:
Given an unsorted array of integers, find the length of longest continuous increasing subsequence (subarray).
Example 1:
1 2 3 4
Input: [1,3,5,4,7] Output: 3 Explanation: The longest continuous increasing subsequence is [1,3,5], its length is 3. Even though [1,3,5,7] is also an increasing subsequence, it's not a continuous one where 5 and 7 are separated by 4.
Example 2:
1 2 3
Input: [2,2,2,2,2] Output: 1 Explanation: The longest continuous increasing subsequence is [2], its length is 1.
找一个最长递增子串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
classSolution { public: intfindLengthOfLCIS(vector<int>& nums){ if(nums.size() == 0) return0; int res = 0, cur = 1; for(int i = 1; i < nums.size(); i ++) { if(nums[i] > nums[i-1]) cur ++; else { res = max(res, cur); cur = 1; } } returnmax(res, cur); } };
Leetcode676. Implement Magic Dictionary
Implement a magic directory with buildDict, and search methods.
For the method buildDict, you’ll be given a list of non-repetitive words to build a dictionary.
For the method search, you’ll be given a word, and judge whether if you modify exactly one character into another character in this word, the modified word is in the dictionary you just built.
classMagicDictionary { public: /** Initialize your data structure here. */ MagicDictionary() {} /** Build a dictionary through a list of words */ voidbuildDict(vector<string> dict){ for (string word : dict) { m[word.size()].push_back(word); } } /** Returns if there is any word in the trie that equals to the given word after modifying exactly one character */ boolsearch(string word){ for (string str : m[word.size()]) { int cnt = 0, i = 0; for (; i < word.size(); ++i) { if (word[i] == str[i]) continue; if (word[i] != str[i] && cnt == 1) break; ++cnt; } if (i == word.size() && cnt == 1) returntrue; } returnfalse; }
classMagicDictionary { public: /** Initialize your data structure here. */ MagicDictionary() {} /** Build a dictionary through a list of words */ voidbuildDict(vector<string> dict){ for (string word : dict) s.insert(word); } /** Returns if there is any word in the trie that equals to the given word after modifying exactly one character */ boolsearch(string word){ for (int i = 0; i < word.size(); ++i) { char t = word[i]; for (char c = 'a'; c <= 'z'; ++c) { if (c == t) continue; word[i] = c; if (s.count(word)) returntrue; } word[i] = t; } returnfalse; } private: unordered_set<string> s; };
Leetcode677. Map Sum Pairs
Implement a MapSum class with insert, and sum methods.
For the method insert, you’ll be given a pair of (string, integer). The string represents the key and the integer represents the value. If the key already existed, then the original key-value pair will be overridden to the new one.
For the method sum, you’ll be given a string representing the prefix, and you need to return the sum of all the pairs’ value whose key starts with the prefix.
classMapSum { public: /** Initialize your data structure here. */ MapSum() {} voidinsert(string key, int val){ m[key] = val; } intsum(string prefix){ int res = 0, n = prefix.size(); for (auto it = m.lower_bound(prefix); it != m.end(); ++it) { if (it->first.substr(0, n) != prefix) break; res += it->second; } return res; } private: map<string, int> m; };
classTrie { public: Trie* child[26]; bool isword; int number; Trie() { for (int i = 0; i < 26; i ++) child[i] = NULL; isword = false; number = 0; } }; classMapSum { public: Trie * root; /** Initialize your data structure here. */ MapSum() { root = newTrie(); } voidinsert(string key, int val){ Trie *cur = root; for (char c : key) { if (cur->child[c-'a'] == NULL) cur->child[c-'a'] = newTrie(); cur = cur->child[c-'a']; } cur->isword = true; cur->number = val; } intsum(string prefix){ Trie *cur = root; int sum = 0; for (char c : prefix) { cur = cur->child[c-'a']; if (!cur) break; } queue<Trie*> q; if (cur) q.push(cur); while(!q.empty()) { Trie* t = q.front(); q.pop(); if (t->number != 0) sum += t->number; for (int i = 0; i < 26; i ++) { if (t->child[i]) q.push(t->child[i]); } } return sum; } };
Leetcode678. Valid Parenthesis String
Given a string containing only three types of characters: ‘(‘, ‘)’ and ‘*’, write a function to check whether this string is valid. We define the validity of a string by these rules:
Any left parenthesis ‘(‘ must have a corresponding right parenthesis ‘)’.
Any right parenthesis ‘)’ must have a corresponding left parenthesis ‘(‘.
Left parenthesis ‘(‘ must go before the corresponding right parenthesis ‘)’.
‘*’ could be treated as a single right parenthesis ‘)’ or a single left parenthesis ‘(‘ or an empty string.
An empty string is also valid.
Example 1:
1 2
Input: "()" Output: True
Example 2:
1 2
Input: "(*)" Output: True
Example 3:
1 2
Input: "(*))" Output: True
Note: The string size will be in the range [1, 100].
classSolution { public: boolcheckValidString(string s){ int left = 0, right = 0, n = s.size(); for (int i = 0; i < n; ++i) { if (s[i] == '(' || s[i] == '*') ++left; else --left; if (left < 0) returnfalse; } if (left == 0) returntrue; for (int i = n - 1; i >= 0; --i) { if (s[i] == ')' || s[i] == '*') ++right; else --right; if (right < 0) returnfalse; } returntrue; } };
The division operator / represents real division, not integer division. For example, 4 / (1 - 2/3) = 12.
Every operation done is between two numbers. In particular, we cannot use - as a unary operator. For example, with [1, 1, 1, 1] as input, the expression -1 - 1 - 1 - 1 is not allowed.
You cannot concatenate numbers together. For example, if the input is [1, 2, 1, 2], we cannot write this as 12 + 12.
Given a list of strings, each string can be one of the 4 following types:
Integer (one round’s score): Directly represents the number of points you get in this round.
“+” (one round’s score): Represents that the points you get in this round are the sum of the last two valid round’s points.
“D” (one round’s score): Represents that the points you get in this round are the doubled data of the last valid round’s points.
“C” (an operation, which isn’t a round’s score): Represents the last valid round’s points you get were invalid and should be removed. Each round’s operation is permanent and could have an impact on the round before and the round after.
You need to return the sum of the points you could get in all the rounds.
Example 1:
1 2 3 4 5 6 7 8
Input: ["5","2","C","D","+"] Output: 30 Explanation: Round 1: You could get 5 points. The sum is: 5. Round 2: You could get 2 points. The sum is: 7. Operation 1: The round 2's data was invalid. The sum is: 5. Round 3: You could get 10 points (the round 2's data has been removed). The sum is: 15. Round 4: You could get 5 + 10 = 15 points. The sum is: 30.
Example 2:
1 2 3 4 5 6 7 8 9 10 11
Input: ["5","-2","4","C","D","9","+","+"] Output: 27 Explanation: Round 1: You could get 5 points. The sum is: 5. Round 2: You could get -2 points. The sum is: 3. Round 3: You could get 4 points. The sum is: 7. Operation 1: The round 3's data is invalid. The sum is: 3. Round 4: You could get -4 points (the round 3's data has been removed). The sum is: -1. Round 5: You could get 9 points. The sum is: 8. Round 6: You could get -4 + 9 = 5 points. The sum is 13. Round 7: You could get 9 + 5 = 14 points. The sum is 27.
classSolution { public: intcalPoints(vector<string>& ops){ vector<int> res(ops.size()); int pointer = 0; for(int i = 0; i < ops.size(); i ++) { if(ops[i] == "C") { pointer --; } elseif(ops[i] == "D") { res[pointer] = res[pointer-1]*2; pointer++; } elseif(ops[i] == "+") { res[pointer] = res[pointer-1]+res[pointer-2]; pointer++; } else { res[pointer] = stoi(ops[i]); pointer++; } } int sum = 0; for(int i = 0;i < pointer; i ++) sum += res[i]; return sum; } };
大佬做法:
1 2 3 4 5 6 7 8 9
intcalPoints(char ** ops, int opsSize){ int p[1000] = { 0 }, r = 0, t = 0; for (int i = 0 ; i < opsSize ; i++) *ops[i] == '+' ? r ? p[r] = p[r - 1], r > 1 ? p[r] += p[r - 2] : 0, t += p[r++] : 0 : *ops[i] == 'D' ? r ? t += p[r] = p[r - 1] * 2, r++ : 0 : *ops[i] == 'C' ? r ? t -= p[--r] : 0 : (t += p[r++] = atoi(ops[i])); return t; }
Leetcode684. Redundant Connection
In this problem, a tree is an undirected graph that is connected and has no cycles.
The given input is a graph that started as a tree with N nodes (with distinct values 1, 2, …, N), with one additional edge added. The added edge has two different vertices chosen from 1 to N, and was not an edge that already existed.
The resulting graph is given as a 2D-array of edges. Each element of edges is a pair [u, v] with u < v, that represents an undirected edge connecting nodes u and v.
Return an edge that can be removed so that the resulting graph is a tree of N nodes. If there are multiple answers, return the answer that occurs last in the given 2D-array. The answer edge [u, v] should be in the same format, with u < v.
Example 1:
1 2 3 4 5 6
Input: [[1,2], [1,3], [2,3]] Output: [2,3] Explanation: The given undirected graph will be like this: 1 / \ 2 - 3
Example 2:
1 2 3 4 5 6
Input: [[1,2], [2,3], [3,4], [1,4], [1,5]] Output: [1,4] Explanation: The given undirected graph will be like this: 5 - 1 - 2 | | 4 - 3
Note:
The size of the input 2D-array will be between 3 and 1000.
Every integer represented in the 2D-array will be between 1 and N, where N is the size of the input array.
这道题给我们了一个无向图,让删掉组成环的最后一条边用并查集做即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
classSolution { public: vector<int> findRedundantConnection(vector<vector<int>>& edges){ vector<int> root(2001, -1); for (auto edge : edges) { int x = find(root, edge[0]), y = find(root, edge[1]); if (x == y) return edge; root[x] = y; } return {}; } intfind(vector<int>& root, int i){ while (root[i] != -1) { i = root[i]; } return i; } };
Leetcode686. Repeated String Match
Given two strings A and B, find the minimum number of times A has to be repeated such that B is a substring of it. If no such solution, return -1.
For example, with A = “abcd” and B = “cdabcdab”.
Return 3, because by repeating A three times (“abcdabcdabcd”), B is a substring of it; and B is not a substring of A repeated two times (“abcdabcd”).
Note: The length of A and B will be between 1 and 10000.
Given a binary tree, find the length of the longest path where each node in the path has the same value. This path may or may not pass through the root.
The length of path between two nodes is represented by the number of edges between them.
Example 1:
1 2 3 4 5 6 7
Input: 5 / \ 4 5 / \ \ 1 1 5 Output: 2
Example 2:
1 2 3 4 5 6 7
Input: 1 / \ 4 5 / \ \ 4 4 5 Output: 2
Note: The given binary tree has not more than 10000 nodes. The height of the tree is not more than 1000. 要求的是最长的路径,所以就不可以往回走,只能是两个节点的路径,那这个路径就可以表示为一个根结点到左边的最长路径+到右边最长路径,简单的对每一个根结点往左往右递归求解就好。 对每一个结点,以其作为根结点,对左右分别dfs求得从当前节点出发左边最长和右边最长的值,然后加起来和max对比,如果大于max则替换max。然后返回上一层,返回上一层的数要为左边或右边的最大值,而不是相加值,因为对于父节点为根结点的话只能往左或者往右寻找最长路径,而不能先去左边然后去右边然后返回。
classSolution { public: intdfs(TreeNode* root, int& maxx){ if (root->left == NULL && root->right == NULL) return0; int l = 0, r = 0; if(root->left && root->left->val == root->val) l = 1 + dfs(root->left, maxx); elseif(root->left) dfs(root->left, maxx); if (root->right && root->right->val == root->val) r = 1 + dfs(root->right, maxx); elseif (root->right) dfs(root->right, maxx); if(l + r > maxx) maxx = l + r; return l > r ? l : r; } intlongestUnivaluePath(TreeNode* root){ int maxx = 0; if(!root) return0; dfs(root, maxx); return maxx; } };
Leetcode690. Employee Importance
You are given a data structure of employee information, which includes the employee’s unique id, his importance value and his direct subordinates’ id.
For example, employee 1 is the leader of employee 2, and employee 2 is the leader of employee 3. They have importance value 15, 10 and 5, respectively. Then employee 1 has a data structure like [1, 15, [2]], and employee 2 has [2, 10, [3]], and employee 3 has [3, 5, []]. Note that although employee 3 is also a subordinate of employee 1, the relationship is not direct.
Now given the employee information of a company, and an employee id, you need to return the total importance value of this employee and all his subordinates.
Example 1:
1 2 3 4
Input: [[1, 5, [2, 3]], [2, 3, []], [3, 3, []]], 1 Output: 11 Explanation: Employee 1 has importance value 5, and he has two direct subordinates: employee 2 and employee 3. They both have importance value 3. So the total importance value of employee 1 is 5 + 3 + 3 = 11.
/* // Definition for Employee. class Employee { public: int id; int importance; vector<int> subordinates; }; */ classSolution { public: intgetImportance(vector<Employee*> employees, int id){ int total = 0; vector<int> subids; for (Employee* employee : employees) { if (employee->id == id) { total += employee->importance; subids = employee->subordinates; break; } } if (subids.size() == 0) return total; for (int subid : subids) total += getImportance(employees, subid); return total; } }; };
Leetcode692. Top K Frequent Words
Given a non-empty list of words, return the k most frequent elements.
Your answer should be sorted by frequency from highest to lowest. If two words have the same frequency, then the word with the lower alphabetical order comes first.
Example 1:
1 2 3 4
Input: ["i", "love", "leetcode", "i", "love", "coding"], k = 2 Output: ["i", "love"] Explanation: "i" and "love" are the two most frequent words. Note that "i" comes before "love" due to a lower alphabetical order.
Example 2:
1 2 3 4
Input: ["the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"], k = 4 Output: ["the", "is", "sunny", "day"] Explanation: "the", "is", "sunny" and "day" are the four most frequent words, with the number of occurrence being 4, 3, 2 and 1 respectively.
这道题让我们求前K个高频词,跟之前那道题 Top K Frequent Elements 极其类似,换了个数据类型就又是一道新题。唯一的不同就是之前那道题对于出现频率相同的数字,没有顺序要求。而这道题对于出现频率相同的单词,需要按照字母顺序来排。但是解法都一样,还是用最小堆和桶排序的方法。首先来看最小堆的方法,思路是先建立每个单词和其出现次数之间的映射,然后把单词和频率的pair放进最小堆,如果没有相同频率的单词排序要求,我们完全可以让频率当作pair的第一项,这样priority_queue默认是以pair的第一项为key进行从大到小的排序,而当第一项相等时,又会以第二项由大到小进行排序,这样第一项的排序方式就与题目要求的相同频率的单词要按字母顺序排列不相符,当然我们可以在存入结果res时对相同频率的词进行重新排序处理,也可以对priority_queue的排序机制进行自定义,这里我们采用第二种方法,我们自定义排序机制,我们让a.second > b.second,让小频率的词在第一位,然后当a.second == b.second时,我们让a.first < b.first,这是让字母顺序大的排在前面(这里博主需要强调一点的是,priority_queue的排序机制的写法和vector的sort的排序机制的写法正好顺序相反,同样的写法,用在sort里面就是频率小的在前面,不信的话可以自己试一下)。定义好最小堆后,我们首先统计单词的出现频率,然后组成pair排序最小堆之中,我们只保存k个pair,超过了就把队首的pair移除队列,最后我们把单词放入结果res中即可,参见代码如下:
classSolution { public: vector<string> topKFrequent(vector<string>& words, int k){ vector<string> res; unordered_map<string, int> freq; map<int, set<string>> m; for (string word : words) ++freq[word]; for (auto a : freq) { m[a.second].insert(a.first); } for (auto it = m.rbegin(); it != m.rend(); ++it) { if (k <= 0) break; auto t = it->second; vector<string> v(t.begin(), t.end()); if (k >= t.size()) { res.insert(res.end(), v.begin(), v.end()); } else { res.insert(res.end(), v.begin(), v.begin() + k); } k -= t.size(); } return res; } };
Leetcode693. Binary Number with Alternating Bits
Given a positive integer, check whether it has alternating bits: namely, if two adjacent bits will always have different values.
Example 1:
1 2 3 4
Input: 5 Output: True Explanation: The binary representation of 5 is: 101
Example 2:
1 2 3 4
Input: 7 Output: False Explanation: The binary representation of 7 is: 111.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
classSolution { public: boolhasAlternatingBits(int n){ int flag = 0; int prev = n & 1; n = n >> 1; while(n) { int temp = n & 1; if(prev == temp) returnfalse; prev = temp; n = n >> 1; } returntrue; } };
Leetcode695. Max Area of Island
Given a non-empty 2D array grid of 0’s and 1’s, an island is a group of 1‘s (representing land) connected 4-directionally (horizontal or vertical.) You may assume all four edges of the grid are surrounded by water.
Find the maximum area of an island in the given 2D array. (If there is no island, the maximum area is 0.)
classSolution { public: vector<vector<int>> dirs{{0,-1},{-1,0},{0,1},{1,0}}; intmaxAreaOfIsland(vector<vector<int>>& grid){ int m = grid.size(), n = grid[0].size(), res = 0; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (grid[i][j] != 1) continue; int cnt = 0; helper(grid, i, j, cnt, res); } } return res; } voidhelper(vector<vector<int>>& grid, int i, int j, int& cnt, int& res){ int m = grid.size(), n = grid[0].size(); if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] <= 0) return; res = max(res, ++cnt); grid[i][j] *= -1; for (auto dir : dirs) { helper(grid, i + dir[0], j + dir[1], cnt, res); } } };
Leetcode696. Count Binary Substrings
Give a string s, count the number of non-empty (contiguous) substrings that have the same number of 0’s and 1’s, and all the 0’s and all the 1’s in these substrings are grouped consecutively.
Substrings that occur multiple times are counted the number of times they occur.
Notice that some of these substrings repeat and are counted the number of times they occur.
Also, “00110011” is not a valid substring because all the 0’s (and 1’s) are not grouped together.
Example 1:
1 2 3
Input: "00110011" Output: 6 Explanation: There are 6 substrings that have equal number of consecutive 1's and 0's: "0011", "01", "1100", "10", "0011", and "01".
Example 2:
1 2 3
Input: "10101" Output: 4 Explanation: There are 4 substrings: "10", "01", "10", "01" that have equal number of consecutive 1's and 0's.
classSolution { public: intcountBinarySubstrings(string s){ int res = 0, prev = 0, cur = 1; for(int i = 1; i < s.length(); i ++) { if(s[i-1] == s[i]) cur ++; else { prev = cur; cur = 1; } if(prev>=cur) res ++; } return res; } };
Leetcode697. Degree of an Array
Given a non-empty array of non-negative integers nums, the degree of this array is defined as the maximum frequency of any one of its elements.
Your task is to find the smallest possible length of a (contiguous) subarray of nums, that has the same degree as nums.
Example 1:
1 2 3 4 5 6 7
Input: [1, 2, 2, 3, 1] Output: 2 Explanation: The input array has a degree of 2 because both elements 1 and 2 appear twice. Of the subarrays that have the same degree: [1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2] The shortest length is 2. So return 2.
classSolution { public: intfindShortestSubArray(vector<int>& nums){ unordered_map<int, int> mp; unordered_map<int, pair<int, int>> mpp; int maxx = -1; for(int i = 0; i < nums.size(); i ++) { if(mp[nums[i]] == 0) mpp[nums[i]] = {i, i}; else mpp[nums[i]].second = i; mp[nums[i]] ++; maxx = max(maxx, mp[nums[i]]); } int res = 9999999; for(auto it = mp.begin(); it != mp.end(); it ++) { if(it->second == maxx) { res = min(res, mpp[it->first].second - mpp[it->first].first + 1); } } return res; } };
记录下每个元素的出现次数和出现的首尾坐标。然后根据首尾坐标计算长度。
Leetcode698. Partition to K Equal Sum Subsets
Given an array of integers nums and a positive integer k, find whether it’s possible to divide this array into knon-empty subsets whose sums are all equal.
Example 1:
1 2 3
Input: nums = [4, 3, 2, 3, 5, 2, 1], k = 4 Output: True Explanation: It's possible to divide it into 4 subsets (5), (1, 4), (2,3), (2,3) with equal sums.
Note:
1 <= k <= len(nums) <= 16.
0 < nums[i] < 10000.
这道题给了我们一个数组nums和一个数字k,问我们该数字能不能分成k个非空子集合,使得每个子集合的和相同。给了k的范围是[1,16],而且数组中的数字都是正数。这跟之前那道 Partition Equal Subset Sum 很类似,但是那道题只让分成两个子集合,所以问题可以转换为是否存在和为整个数组和的一半的子集合,可以用dp来做。但是这道题让求k个和相同的,感觉无法用dp来做,因为就算找出了一个,其余的也需要验证。这道题我们可以用递归来做,首先我们还是求出数组的所有数字之和sum,首先判断sum是否能整除k,不能整除的话直接返回false。然后需要一个visited数组来记录哪些数组已经被选中了,然后调用递归函数,我们的目标是组k个子集合,是的每个子集合之和为target = sum/k。我们还需要变量start,表示从数组的某个位置开始查找,curSum为当前子集合之和,在递归函数中,如果k=1,说明此时只需要组一个子集合,那么当前的就是了,直接返回true。如果curSum等于target了,那么我们再次调用递归,此时传入k-1,start和curSum都重置为0,因为我们当前又找到了一个和为target的子集合,要开始继续找下一个。否则的话就从start开始遍历数组,如果当前数字已经访问过了则直接跳过,否则标记为已访问。然后调用递归函数,k保持不变,因为还在累加当前的子集合,start传入i+1,curSum传入curSum+nums[i],因为要累加当前的数字,如果递归函数返回true了,则直接返回true。否则就将当前数字重置为未访问的状态继续遍历,参见代码如下:
classSolution { public: boolcanPartitionKSubsets(vector<int>& nums, int k){ int sum = accumulate(nums.begin(), nums.end(), 0); if (sum % k != 0) returnfalse; vector<bool> visited(nums.size(), false); returnhelper(nums, k, sum / k, 0, 0, visited); } boolhelper(vector<int>& nums, int k, int target, int start, int curSum, vector<bool>& visited){ if (k == 1) returntrue; if (curSum == target) returnhelper(nums, k - 1, target, 0, 0, visited); for (int i = start; i < nums.size(); ++i) { if (visited[i]) continue; visited[i] = true; if (helper(nums, k, target, i + 1, curSum + nums[i], visited)) returntrue; visited[i] = false; } returnfalse; } };
Given the root node of a binary search tree (BST) and a value. You need to find the node in the BST that the node’s value equals the given value. Return the subtree rooted with that node. If such node doesn’t exist, you should return NULL.
For example,
Given the tree:
1 2 3 4 5
4 / \ 2 7 / \ 1 3
And the value to search: 2 You should return this subtree:
1 2 3
2 / \ 1 3
In the example above, if we want to search the value 5, since there is no node with value 5, we should return NULL.
Note that an empty tree is represented by NULL, therefore you would see the expected output (serialized tree format) as [], not null.
Write a class StockSpanner which collects daily price quotes for some stock, and returns the span of that stock’s price for the current day.
The span of the stock’s price today is defined as the maximum number of consecutive days (starting from today and going backwards) for which the price of the stock was less than or equal to today’s price.
For example, if the price of a stock over the next 7 days were [100, 80, 60, 70, 60, 75, 85], then the stock spans would be [1, 1, 1, 2, 1, 4, 6].
Example 1:
1 2 3 4 5 6 7 8 9 10 11
Input: ["StockSpanner","next","next","next","next","next","next","next"], [[],[100],[80],[60],[70],[60],[75],[85]] Output: [null,1,1,1,2,1,4,6] Explanation: First, S = StockSpanner() is initialized. Then: S.next(100) is called and returns 1, S.next(80) is called and returns 1, S.next(60) is called and returns 1, S.next(70) is called and returns 2, S.next(60) is called and returns 1, S.next(75) is called and returns 4, S.next(85) is called and returns 6.
Note that (for example) S.next(75) returned 4, because the last 4 prices (including today’s price of 75) were less than or equal to today’s price.
Note:
Calls to StockSpanner.next(int price)will have 1 <= price <= 10^5.
There will be at most 10000 calls to StockSpanner.next per test case.
There will be at most 150000 calls to StockSpanner.next across all test cases.
The total time limit for this problem has been reduced by 75% for C++, and 50% for all other languages.
这道题定义了一个 StockSpanner 的类,有一个 next 函数,每次给当天的股价,让我们返回之前连续多少天都是小于等于当前股价。
可以找连续递增的子数组的长度么,其实也是不行的,就拿题目中的例子来说吧 [100, 80, 60, 70, 60, 75, 85],数字 75 前面有三天是比它小的,但是这三天不是有序的,是先增后减的,那怎么办呢?我们先从简单的情况分析,假如当前的股价要小于前一天的,那么连续性直接被打破了,所以此时直接返回1就行了。但是假如大于等于前一天股价的话,情况就比较 tricky 了,因为此时所有小于等于前一天股价的天数肯定也是小于等于当前的,那么我们就需要知道是哪一天的股价刚好大于前一天的股价,然后用这一天的股价跟当前的股价进行比较,若大于当前股价,说明当前的连续天数就是前一天的连续天数加1,而若小于当前股价,我们又要重复这个过程,去比较刚好大于之前那个的股价。所以我们需要知道对于每一天,往前推刚好大于当前股价的是哪一天,用一个数组 pre,其中 pre[i] 表示从第i天往前推刚好大于第i天的股价的是第 pre[i] 天。接下来看如何实现 next 函数,首先将当前股价加入 nums 数组,然后前一天在数组中的位置就是 (int)nums.size()-2。再来想想 corner case 的情况,假如当前是数组中的第0天,前面没有任何股价了,我们的 pre[0] 就赋值为 -1 就行了,怎么知道当前是否是第0天,就看 pre 数组是否为空。再有就是由于i要不断去 pre 数组中找到之前的天数,所以最终i是有可能到达 pre[0] 的,那么就要判断当i为 -1 时,也要停止循环。循环的最后一个条件就是当之前的股价小于等当前的估计 price 时,才进行循环,这个前面讲过了,循环内部就是将 pre[i] 赋值给i,这样就完成了跳到之前天的操作。while 循环结束后要将i加入到 pre 数组,因为这个i就是从当前天往前推,一个大于当前股价的那一天,有了这个i,就可以计算出连续天数了,参见代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
classStockSpanner { public: StockSpanner() {}
intnext(int price){ nums.push_back(price); int i = (int)nums.size() - 2; while (!pre.empty() && i >= 0 && nums[i] <= price) { i = pre[i]; } pre.push_back(i); return (int)pre.size() - 1 - i; }
private: vector<int> nums, pre; };
我们还可以使用栈来做,里面放一个 pair 对儿,分别是当前的股价和之前比其小的连续天数。在 next 函数中,使用一个 cnt 变量,初始化为1。还是要个 while 循环,其实核心的本质都是一样的,循环的条件首先是栈不能为空,并且栈顶元素的股价小于等于当前股价,那么 cnt 就加上栈顶元素的连续天数,可以感受到跟上面解法在这里的些许不同之处了吧,之前是一直找到第一个大于当前股价的天数在数组中的位置,然后相减得到连续天数,这里是在找的过程中直接累加连续天数,最终都可以得到正确的结果,参见代码如下:
In a row of trees, the i-th tree produces fruit with type tree[i].
You start at any tree of your choice, then repeatedly perform the following steps:
Add one piece of fruit from this tree to your baskets. If you cannot, stop. Move to the next tree to the right of the current tree. If there is no tree to the right, stop. Note that you do not have any choice after the initial choice of starting tree: you must perform step 1, then step 2, then back to step 1, then step 2, and so on until you stop.
You have two baskets, and each basket can carry any quantity of fruit, but you want each basket to only carry one type of fruit each.
What is the total amount of fruit you can collect with this procedure?
Example 1:
1 2 3
Input: [1,2,1] Output: 3 Explanation: We can collect [1,2,1].
Example 2:
1 2 3
Input: [0,1,2,2] Output: 3 Explanation: We can collect [1,2,2]. If we started at the first tree, we would only collect [0, 1].
Example 3:
1 2 3
Input: [1,2,3,2,2] Output: 4 Explanation: We can collect [2,3,2,2]. If we started at the first tree, we would only collect [1, 2].
Example 4:
1 2 3
Input: [3,3,3,1,2,1,1,2,3,3,4] Output: 5 Explanation: We can collect [1,2,1,1,2]. If we started at the first tree or the eighth tree, we would only collect 4 fruits.
Note:
1 <= tree.length <= 40000
0 <= tree[i] < tree.length
这道题说是给了我们一排树,每棵树产的水果种类是 tree[i],说是现在有两种操作,第一种是将当前树的水果加入果篮中,若不能加则停止;第二种是移动到下一个树,若没有下一棵树,则停止。现在我们有两个果篮,可以从任意一个树的位置开始,但是必须按顺序执行操作一和二,问我们最多能收集多少个水果。说实话这道题的题目描述确实不太清晰,博主看了很多遍才明白意思,论坛上也有很多吐槽的帖子,但实际上这道题的本质就是从任意位置开始,若最多只能收集两种水果,问最多能收集多少个水果。那么再进一步提取,其实就是最多有两个不同字符的最长子串的长度,跟之前那道 Longest Substring with At Most Two Distinct Characters 一模一样,只不过换了一个背景,代码基本都可以直接使用的,博主感觉这样出题有点不太好吧,完全重复了。之前那题的四种解法这里完全都可以使用,先来看第一种,使用一个 HashMap 来记录每个水果出现次数,当 HashMap 中当映射数量超过两个的时候,我们需要删掉一个映射,做法是滑动窗口的左边界 start 的水果映射值减1,若此时减到0了,则删除这个映射,否则左边界右移一位。当映射数量回到两个的时候,用当前窗口的大小来更新结果 res 即可,参见代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
classSolution { public: inttotalFruit(vector& tree){ int res = 0, start = 0, n = tree.size(); unordered_map<int, int> fruitCnt; for (int i = 0; i < n; ++i) { ++fruitCnt[tree[i]]; while (fruitCnt.size() > 2) { if (--fruitCnt[tree[start]] == 0) { fruitCnt.erase(tree[start]); } ++start; } res = max(res, i - start + 1); } return res; } };
classSolution { public: inttotalFruit(vector& tree){ int res = 0, start = 0, n = tree.size(); unordered_map<int, int> fruitPos; for (int i = 0; i < n; ++i) { fruitPos[tree[i]] = i; while (fruitPos.size() > 2) { if (fruitPos[tree[start]] == start) { fruitPos.erase(tree[start]); } ++start; } res = max(res, i - start + 1); } return res; } };
后来又在网上看到了一种解法,这种解法是维护一个滑动窗口 sliding window,指针 left 指向起始位置,right 指向 window 的最后一个位置,用于定位 left 的下一个跳转位置,思路如下:
若当前字符和前一个字符相同,继续循环。
若不同,看当前字符和 right 指的字符是否相同:
若相同,left 不变,右边跳到 i - 1。
若不同,更新结果,left 变为 right+1,right 变为 i - 1。
最后需要注意在循环结束后,我们还要比较结果 res 和 n - left 的大小,返回大的,这是由于如果数组是 [5,3,5,2,1,1,1],那么当 left=3 时,i=5,6 的时候,都是继续循环,当i加到7时,跳出了循环,而此时正确答案应为 [2,1,1,1] 这4个数字,而我们的结果 res 只更新到了 [5,3,5] 这3个数字,所以我们最后要判断 n - left 和结果 res 的大小。
另外需要说明的是这种解法仅适用于于不同字符数为2个的情况,如果为k个的话,还是需要用上面两种解法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
classSolution { public: inttotalFruit(vector<int>& tree){ int res = 0, left = 0, right = -1, n = tree.size(); for (int i = 1; i < n; ++i) { if (tree[i] == tree[i - 1]) continue; if (right >= 0 && tree[right] != tree[i]) { res = max(res, i - left); left = right + 1; } right = i - 1; } returnmax(n - left, res); } };
还有一种不使用 HashMap 的解法,这里我们使用若干个变量,其中 cur 为当前最长子数组的长度,a和b为当前候选的两个不同的水果种类,cntB 为水果b的连续个数。我们遍历所有数字,假如遇到的水果种类是a和b中的任意一个,那么 cur 可以自增1,否则 cntB 自增1,因为若是新水果种类的话,默认已经将a种类淘汰了,此时候选水果由类型b和这个新类型水果构成,所以当前长度是 cntB+1。然后再来更新 cntB,假如当前水果种类是b的话,cntB 自增1,否则重置为1,因为 cntB 统计的就是水果种类b的连续个数。然后再来判断,若当前种类不是b,则此时a赋值为b, b赋值为新种类。最后不要忘了用 cur 来更新结果 res,参见代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
classSolution { public: inttotalFruit(vector<int>& tree){ int res = 0, cur = 0, cntB = 0, a = 0, b = 0; for (int fruit : tree) { cur = (fruit == a || fruit == b) ? cur + 1 : cntB + 1; cntB = (fruit == b) ? cntB + 1 : 1; if (b != fruit) { a = b; b = fruit; } res = max(res, cur); } return res; } };
Leetcode905. Sort Array By Parity
Given an array A of non-negative integers, return an array consisting of all the even elements of A, followed by all the odd elements of A.
You may return any answer array that satisfies this condition.
Example 1:
1 2 3
Input: [3,1,2,4] Output: [2,4,3,1] The outputs [4,2,3,1], [2,4,1,3], and [4,2,1,3] would also be accepted.
classSolution { public: intsumSubarrayMins(vector<int>& A){ int res = 0, n = A.size(), M = 1e9 + 7; stack<pair<int, int>> st_pre, st_next; vector<int> left(n), right(n); for (int i = 0; i < n; ++i) { while (!st_pre.empty() && st_pre.top().first > A[i]) { st_pre.pop(); } left[i] = st_pre.empty() ? (i + 1) : (i - st_pre.top().second); st_pre.push({A[i], i}); right[i] = n - i; while (!st_next.empty() && st_next.top().first > A[i]) { auto t = st_next.top(); st_next.pop(); right[t.second] = i - t.second; } st_next.push({A[i], i}); } for (int i = 0; i < n; ++i) { res = (res + A[i] * left[i] * right[i]) % M; } return res; } };
我们也可以对上面的解法进行空间上的优化,只用一个单调栈,用来记录当前数字之前的第一个小的数字的位置,然后遍历每个数字,但是要多遍历一个数字,i从0遍历到n,当 i=n 时,cur 赋值为0,否则赋值为 A[i]。然后判断若栈不为空,且 cur 小于栈顶元素,则取出栈顶元素位置 idx,由于是单调栈,那么新的栈顶元素就是 A[idx] 前面第一个较小数的位置,由于此时栈可能为空,所以再去之前要判断一下,若为空,则返回 -1,否则返回栈顶元素,用 idx 减去栈顶元素就是以 A[idx] 为结尾且最小值为 A[idx] 的子数组的个数,然后用i减去 idx 就是以 A[idx] 为起始且最小值为 A[idx] 的子数组的个数,然后 A[idx] x left x right 就是 A[idx] 这个数字当子数组的最小值之和,累加到结果 res 中并对超大数取余即可,参见代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
classSolution { public: intsumSubarrayMins(vector<int>& A){ int res = 0, n = A.size(), M = 1e9 + 7; stack<int> st; for (int i = 0; i <= n; ++i) { int cur = (i == n) ? 0 : A[i]; while (!st.empty() && cur < A[st.top()]) { int idx = st.top(); st.pop(); int left = idx - (st.empty() ? -1 : st.top()); int right = i - idx; res = (res + A[idx] * left * right) % M; } st.push(i); } return res; } };
Leetcode908. Smallest Range I
Given an array A of integers, for each integer A[i] we may choose any x with -K <= x <= K, and add x to A[i]. After this process, we have some array B. Return the smallest possible difference between the maximum value of B and the minimum value of B.
Example 1:
1 2 3
Input: A = [1], K = 0 Output: 0 Explanation: B = [1]
Example 2:
1 2 3
Input: A = [0,10], K = 2 Output: 6 Explanation: B = [2,8]
Example 3:
1 2 3
Input: A = [1,3,6], K = 3 Output: 0 Explanation: B = [3,3,3] or B = [4,4,4]
classSolution { public: intsmallestRangeI(vector<int>& A, int K){ // find max and min int maxx = INT_MIN; int minn = INT_MAX; for(int i=0;i<A.size();i++){ if(A[i]>maxx) maxx = A[i]; if(A[i]<minn) minn = A[i]; } if(minn+K >= maxx-K) return0; else return maxx - minn - 2 * K; } };
Leetcode909. Snakes and Ladders
On an N x N board, the numbers from 1 to N*N are written boustrophedonically starting from the bottom left of the board, and alternating direction each row. For example, for a 6 x 6 board, the numbers are written as follows:
You start on square 1 of the board (which is always in the last row and first column). Each move, starting from square x, consists of the following:
You choose a destination square S with number x+1, x+2, x+3, x+4, x+5, or x+6, provided this number is <= N*N. (This choice simulates the result of a standard 6-sided die roll: ie., there are always at most 6 destinations, regardless of the size of the board.)
If S has a snake or ladder, you move to the destination of that snake or ladder. Otherwise, you move to S.
A board square on row r and column c has a “snake or ladder” if board[r][c] != -1. The destination of that snake or ladder is board[r][c].
Note that you only take a snake or ladder at most once per move: if the destination to a snake or ladder is the start of another snake or ladder, you do not continue moving. (For example, if the board is [[4,-1],[-1,3]], and on the first move your destination square is 2, then you finish your first move at 3, because you do notcontinue moving to 4.)
Return the least number of moves required to reach square N*N. If it is not possible, return -1.
Example 1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Input: [ [-1,-1,-1,-1,-1,-1], [-1,-1,-1,-1,-1,-1], [-1,-1,-1,-1,-1,-1], [-1,35,-1,-1,13,-1], [-1,-1,-1,-1,-1,-1], [-1,15,-1,-1,-1,-1]] Output: 4 Explanation: At the beginning, you start at square 1 [at row 5, column 0]. You decide to move to square 2, and must take the ladder to square 15. You then decide to move to square 17 (row 3, column 5), and must take the snake to square 13. You then decide to move to square 14, and must take the ladder to square 35. You then decide to move to square 36, ending the game. It can be shown that you need at least 4 moves to reach the N*N-th square, so the answer is 4.
这道题给了一个 NxN 大小的二维数组,从左下角从1开始,蛇形游走,到左上角或者右上角到数字为 NxN,中间某些位置会有梯子,就如同传送门一样,直接可以到达另外一个位置。现在就如同玩大富翁 Big Rich Man 一样,有一个骰子,可以走1到6内的任意一个数字,现在奢侈一把,有无限个遥控骰子,每次都可以走1到6以内指定的步数,问最小能用几步快速到达终点 NxN 位置。博主刚开始做这道题的时候,看是求极值,以为是一道动态规划 Dynamic Programming 的题,结果发现木有办法重现子问题,没法写出状态转移方程,只得作罢。但其实博主忽略了一点,求最小值还有一大神器,广度优先搜索 BFS,最直接的应用就是在迷宫遍历的问题中,求从起点到终点的最少步数,也可以用在更 general 的场景,只要是存在确定的状态转移的方式,可能也可以使用。这道题基本就是类似迷宫遍历的问题,可以走的1到6步可以当作六个方向,这样就可以看作是一个迷宫了,唯一要特殊处理的就是遇见梯子的情况,要跳到另一个位置。这道题还有另一个难点,就是数字标号和数组的二维坐标的转换,这里起始点是在二维数组的左下角,且是1,而代码中定义的二维数组的 (0, 0) 点是在左上角,需要转换一下,还有就是这道题的数字是蛇形环绕的,即当行号是奇数的时候,是从右往左遍历的,转换的时候要注意一下。
classSolution { public: intsnakesAndLadders(vector<vector<int>>& board){ int len = board.size(), res = 0; len = len*len; queue<int> q{{1}}; vector<bool> visited(len + 1); while (!q.empty()) { int s = q.size(); for (int i = 0; i < s; i ++) { int num = q.front(); q.pop(); if (num == len) return res; for (int i = 1; i <= 6 && num + i <= len; ++i) { int next = getBoardValue(board, num + i); if (next == -1) next = num + i; if (visited[next]) continue; visited[next] = true; q.push(next); } } res ++; } return-1; } intgetBoardValue(vector<vector<int>>& board, int i){ int len = board.size(), x = (i - 1) / len, y = (i - 1) % len; if (x % 2 == 1) y = len - 1 - y; x = len - 1 - x; return board[x][y]; } };
Leetcode911. Online Election
In an election, the i-th vote was cast for persons[i] at time times[i].
Now, we would like to implement the following query function: TopVotedCandidate.q(int t) will return the number of the person that was leading the election at time t.
Votes cast at time t will count towards our query. In the case of a tie, the most recent vote (among tied candidates) wins.
Example 1:
1 2 3 4 5 6 7
Input: ["TopVotedCandidate","q","q","q","q","q","q"], [[[0,1,1,0,0,1,0],[0,5,10,15,20,25,30]],[3],[12],[25],[15],[24],[8]] Output: [null,0,1,1,0,0,1] Explanation: At time 3, the votes are [0], and 0 is leading. At time 12, the votes are [0,1,1], and 1 is leading. At time 25, the votes are [0,1,1,0,0,1], and 1 is leading (as ties go to the most recent vote.) This continues for 3 more queries at time 15, 24, and 8.
Note:
1 <= persons.length = times.length <= 5000
0 <= persons[i] <= persons.length
times is a strictly increasing array with all elements in [0, 10^9].
TopVotedCandidate.q is called at most 10000 times per test case.
TopVotedCandidate.q(int t) is always called with t >= times[0].
这道题是关于线上选举的问题,这里给了两个数组 persons 和 times,表示在某个时间点times[i],i这个人把选票投给了 persons[i],现在有一个q函数,输入时间点t,让返回在时间点t时得票最多的人,当得票数相等时,返回最近得票的人。因为查询需求的时间点是任意的,在某个查询时间点可能并没有投票发生,但需要知道当前的票王,当然最傻的办法就是每次都从开头统计到当前时间点,找出票王,但这种方法大概率会超时,正确的方法实际上是要在某个投票的时间点,都统计出当前的票王,然后在查询的时候,查找刚好大于查询时间点的下一个投票时间点,返回前一个时间点的票王即可,所以这里可以使用一个 TreeMap 来建立投票时间点和当前票王之间的映射。如何统计每个投票时间点的票王呢,可以使用一个 count 数组,其中count[i]就表示当前i获得的票数,还需要一个变量 lead,表示当前的票王。现在就可以开始遍历所有的投票了,对于每个投票,将票数加到 count 中对应的人身上,然后跟 lead 比较,若当前人的票数大于等于 lead 的票数,则 lead 更换为当前人,同时建立当前时间点和票王之间的映射。在查询的时候,由于时间点是有序的,所以可以使用二分搜索法,由于使用的是 TreeMap,具有自动排序的功能,可以直接用upper_bound来查找第一个比t大的投票时间,然后再返回上一个投票时间点的票王即可,参见代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
classTopVotedCandidate { public: TopVotedCandidate(vector& persons, vector& times) { int n = persons.size(), lead = 0; vector<int> count(n + 1); for (int i = 0; i < n; ++i) { if (++count[persons[i]] >= count[lead]) { lead = persons[i]; } m[times[i]] = lead; } } intq(int t){ return (--m.upper_bound(t))->second; }
classSolution { public: boolhasGroupsSizeX(vector<int>& deck){ unordered_map<int, int> mapp; for(int i = 0; i < deck.size(); i ++) mapp[deck[i]] ++; int minn = 9999999; for(auto it = mapp.begin(); it != mapp.end(); it ++) minn = min(minn, it->second); int flag; for(int i = 2; i <= minn; i ++) { flag = 0; for(auto it = mapp.begin(); it != mapp.end(); it ++) { if(it->second % i != 0) { flag = 1; break; } } if(flag == 0) returntrue; } returnfalse; } };
Leetcode915. Partition Array into Disjoint Intervals
Given an array A, partition it into two (contiguous) subarrays left and right so that:
Every element in left is less than or equal to every element in right.
left and right are non-empty.
left has the smallest possible size.
Return the length of left after such a partitioning. It is guaranteed that such a partitioning exists.
Example 1:
1 2 3
Input: [5,0,3,8,6] Output: 3 Explanation: left = [5,0,3], right = [8,6]
Example 2:
1 2 3
Input: [1,1,1,0,6,12] Output: 4 Explanation: left = [1,1,1,0], right = [6,12]
Note:
2 <= A.length <= 30000
0 <= A[i] <= 10^6
It is guaranteed there is at least one way to partition A as described.
这道题说是给了一个数组A,让我们分成两个相邻的子数组 left 和 right,使得 left 中的所有数字小于等于 right 中的,并限定了每个输入数组必定会有这么一个分割点,让返回数组 left 的长度。这道题并不算一道难题,当然最简单并暴力的方法就是遍历所有的分割点,然后去验证左边的数组是否都小于等于右边的数,这种写法估计会超时,这里就不去实现了。直接来想优化解法吧,由于分割成的 left 和 right 数组本身不一定是有序的,只是要求 left 中的最大值要小于等于 right 中的最小值,只要这个条件满足了,一定就是符合题意的分割。left 数组的最大值很好求,在遍历数组的过程中就可以得到,而 right 数组的最小值怎么求呢?其实可以反向遍历数组,并且使用一个数组 backMin,其中 backMin[i] 表示在范围 [i, n-1] 范围内的最小值,有了这个数组后,再正向遍历一次数组,每次更新当前最大值 curMax,这就是范围 [0, i] 内的最大值,通过 backMin 数组快速得到范围 [i+1, n-1] 内的最小值,假如 left 的最大值小于等于 right 的最小值,则 i+1 就是 left 的长度,直接返回即可,参见代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
classSolution { public: intpartitionDisjoint(vector& A){ int n = A.size(), curMax = INT_MIN; vector<int> backMin(n, A.back()); for (int i = n - 2; i >= 0; --i) { backMin[i] = min(backMin[i + 1], A[i]); } for (int i = 0; i < n - 1; ++i) { curMax = max(curMax, A[i]); if (curMax <= backMin[i + 1]) return i + 1; } return0; } };
下面来看论坛上的主流解法,只需要一次遍历即可,并且不需要额外的空间,这里使用三个变量,partitionIdx 表示分割点的位置,preMax 表示 left 中的最大值,curMax 表示当前的最大值。思路是遍历每个数字,更新当前最大值 curMax,并且判断若当前数字 A[i] 小于 preMax,说明这个数字也一定是属于 left 数组的,此时整个遍历到的区域应该都是属于 left 的,所以 preMax 要更新为 curMax,并且当前位置也就是潜在的分割点,所以 partitionIdx 更新为i。由于题目中限定了一定会有分割点,所以这种方法是可以得到正确结果的,参见代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
classSolution { public: intpartitionDisjoint(vector& A){ int partitionIdx = 0, preMax = A[0], curMax = preMax; for (int i = 1; i < A.size(); ++i) { curMax = max(curMax, A[i]); if (A[i] < preMax) { preMax = curMax; partitionIdx = i; } } return partitionIdx + 1; } };
Leetcode916. Word Subsets
We are given two arrays A and B of words. Each word is a string of lowercase letters.
Now, say that word b is a subset of word a if every letter in b occurs in a, including multiplicity. For example, “wrr” is a subset of “warrior”, but is not a subset of “world”.
Now say a word a from A is universal if for every b in B, b is a subset of a.
Return a list of all universal words in A. You can return the words in any order.
Example 1:
1 2
Input: A = ["amazon","apple","facebook","google","leetcode"], B = ["e","o"] Output: ["facebook","google","leetcode"]
Example 2:
1 2
Input: A = ["amazon","apple","facebook","google","leetcode"], B = ["l","e"] Output: ["apple","google","leetcode"]
Example 3:
1 2
Input: A = ["amazon","apple","facebook","google","leetcode"], B = ["e","oo"] Output: ["facebook","google"]
Example 4:
1 2
Input: A = ["amazon","apple","facebook","google","leetcode"], B = ["lo","eo"] Output: ["google","leetcode"]
Example 5:
1 2
Input: A = ["amazon","apple","facebook","google","leetcode"], B = ["ec","oc","ceo"] Output: ["facebook","leetcode"]
Note:
1 <= A.length, B.length <= 10000
1 <= A[i].length, B[i].length <= 10
A[i] and B[i] consist only of lowercase letters.
All words in A[i] are unique: there isn’t i != j with A[i] == A[j].
classSolution { public: vector<string> wordSubsets(vector<string>& words1, vector<string>& words2){ vector<int> char_num(26, 0); vector<string> res; for (int i = 0; i < words2.size(); i ++) { vector<int> t = helper(words2[i]); for (int j = 0; j < 26; j ++) char_num[j] = max(char_num[j], t[j]); } for (int i = 0; i < words1.size(); i ++) { vector<int> t = helper(words1[i]); int j = 0; for (j = 0; j < 26; j ++) if (t[j] < char_num[j]) break; if (j == 26) res.push_back(words1[i]); } return res; } vector<int> helper(string a){ vector<int> res(26, 0); for (int i = 0; i < a.length(); i ++) res[a[i] - 'a'] ++; return res; } };
Leetcode917. Reverse Only Letters
Given a string S, return the “reversed” string where all characters that are not a letter stay in the same place, and all letters reverse their positions.
classSolution { public: boolisletter(char b){ if ((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')) returntrue; else returnfalse; } string reverseOnlyLetters(string S){ int i = 0, j = S.length(); while(i < j) { if(!isletter(S[i])) i ++; elseif(!isletter(S[j])) j --; else { char c = S[i]; S[i] = S[j]; S[j] = c; i ++; j --; } } return S; } };
Leetcode918. Maximum Sum Circular Subarray
Given a circular array C of integers represented by A, find the maximum possible sum of a non-empty subarray of C.
Here, a circular array means the end of the array connects to the beginning of the array. (Formally, C[i] = A[i]when 0 <= i < A.length, and C[i+A.length] = C[i] when i >= 0.)
Also, a subarray may only include each element of the fixed buffer A at most once. (Formally, for a subarray C[i], C[i+1], ..., C[j], there does not exist i <= k1, k2 <= j with k1 % A.length = k2 % A.length.)
Example 1:
1 2 3
Input: [1,-2,3,-2] Output: 3 Explanation: Subarray [3] has maximum sum 3
Example 2:
1 2
Input: [5,-3,5] Output: 10 Explanation: Subarray [5,5] has maximum sum 5 + 5 = 10
Example 3:
1 2 3
Input: [3,-1,2,-1] Output: 4 Explanation: Subarray [2,-1,3] has maximum sum 2 + (-1) + 3 = 4
Example 4:
1 2
Input: [3,-2,2,-3] Output: 3 Explanation: Subarray [3] and [3,-2,2] both have maximum sum 3
Example 5:
1 2
Input: [-2,-3,-1] Output: -1 Explanation: Subarray [-1] has maximum sum -1
Note:
-30000 <= A[i] <= 30000
1 <= A.length <= 30000
这道题让求环形子数组的最大和,既然是子数组,则意味着必须是相连的数字,而由于环形数组的存在,说明可以首尾相连,这样的话,最长子数组的范围可以有两种情况,一种是正常的,数组中的某一段子数组,另一种是分为两段的,即首尾相连的。对于第一种情况,其实就是之前那道题 Maximum Subarray 的做法,对于第二种情况,需要转换一下思路,除去两段的部分,中间剩的那段子数组其实是和最小的子数组,只要用之前的方法求出子数组的最小和,用数组总数字和一减,同样可以得到最大和。两种情况的最大和都要计算出来,取二者之间的较大值才是真正的和最大的子数组。但是这里有个 corner case 需要注意一下,假如数组中全是负数,那么和最小的子数组就是原数组本身,则求出的差值是0,而第一种情况求出的和最大的子数组也应该是负数,那么二者一比较,返回0就不对了,所以这种特殊情况需要单独处理一下,参见代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
classSolution { public: intmaxSubarraySumCircular(vector& A){ int sum = 0, mn = INT_MAX, mx = INT_MIN, curMax = 0, curMin = 0; for (int num : A) { curMin = min(curMin + num, num); mn = min(mn, curMin); curMax = max(curMax + num, num); mx = max(mx, curMax); sum += num; } return (sum - mn == 0) ? mx : max(mx, sum - mn); } };
Leetcode921. Minimum Add to Make Parentheses Valid
Given a string S of ‘(‘ and ‘)’ parentheses, we add the minimum number of parentheses ( ‘(‘ or ‘)’, and in any positions ) so that the resulting parentheses string is valid.
Formally, a parentheses string is valid if and only if:
It is the empty string, or It can be written as AB (A concatenated with B), where A and B are valid strings, or It can be written as (A), where A is a valid string. Given a parentheses string, return the minimum number of parentheses we must add to make the resulting string valid.
Example 1:
1 2
Input: "())" Output: 1
Example 2:
1 2
Input: "(((" Output: 3
Example 3:
1 2
Input: "()" Output: 0
Example 4:
1 2
Input: "()))((" Output: 4
Note:
S.length <= 1000 S only consists of ‘(‘ and ‘)’ characters.
classSolution { public: vector<int> sortArrayByParityII(vector<int>& A){ int j = 1; for(int i = 0; i < A.size(); i += 2){ if(A[i] % 2 == 1){ while(A[j] % 2 == 1) j += 2; int temp = A[i]; A[i] = A[j]; A[j] = temp; } } return A; } };
Leetcode925. Long Pressed Name
Your friend is typing his name into a keyboard. Sometimes, when typing a character c, the key might get long pressed, and the character will be typed 1 or more times.
You examine the typed characters of the keyboard. Return True if it is possible that it was your friends name, with some characters (possibly none) being long pressed.
Example 1:
1 2 3
Input: name = "alex", typed = "aaleex" Output: true Explanation: 'a' and 'e' in 'alex' were long pressed.
Example 2:
1 2 3
Input: name = "saeed", typed = "ssaaedd" Output: false Explanation: 'e' must have been pressed twice, but it wasn't in the typed output.
Example 3:
1 2
Input: name = "leelee", typed = "lleeelee" Output: true
Example 4:
1 2 3
Input: name = "laiden", typed = "laiden" Output: true Explanation: It's not necessary to long press any character.
Constraints:
1 <= name.length <= 1000
1 <= typed.length <= 1000
The characters of name and typed are lowercase letters.
A string of ‘0’s and ‘1’s is monotone increasing if it consists of some number of ‘0’s (possibly 0), followed by some number of ‘1’s (also possibly 0.)
We are given a string S of ‘0’s and ‘1’s, and we may flip any ‘0’ to a ‘1’ or a ‘1’ to a ‘0’.
Return the minimum number of flips to make S monotone increasing.
Example 1:
1 2 3
Input: "00110" Output: 1 Explanation: We flip the last digit to get 00111.
Example 2:
1 2 3
Input: "010110" Output: 2 Explanation: We flip to get 011111, or alternatively 000111.
Example 3:
1 2 3
Input: "00011000" Output: 2 Explanation: We flip to get 00000000.
classSolution { public: intminFlipsMonoIncr(string S){ int n = S.size(), res = INT_MAX; vector<int> cnt1(n + 1), cnt0(n + 1); for (int i = 1, j = n - 1; j >= 0; ++i, --j) { cnt1[i] += cnt1[i - 1] + (S[i - 1] == '0' ? 0 : 1); cnt0[j] += cnt0[j + 1] + (S[j] == '1' ? 0 : 1); } for (int i = 0; i <= n; ++i) res = min(res, cnt1[i] + cnt0[i]); return res; } };
我们可以进一步优化一下空间复杂度,用一个变量 cnt1 来记录当前位置时1出现的次数,同时 res 表示使到当前位置的子串变为单调串的翻转次数,用来记录0的个数,因为遇到0就翻1一定可以组成单调串,但不一定是最优解,每次都要和 cnt1 比较以下,若 cnt1 较小,就将 res 更新为 cnt1,此时保证了到当前位置的子串变为单调串的翻转次数最少,并不关心到底是把0变为1,还是1变为0了,其实核心思想跟上面的解法很相近,参见代码如下:
1 2 3 4 5 6 7 8 9 10 11
classSolution { public: intminFlipsMonoIncr(string S){ int n = S.size(), res = 0, cnt1 = 0; for (int i = 0; i < n; ++i) { (S[i] == '0') ? ++res : ++cnt1; res = min(res, cnt1); } return res; } };
Leetcode929. Unique Email Addresses
Every email consists of a local name and a domain name, separated by the @ sign. For example, in alice@leetcode.com, alice is the local name, and leetcode.com is the domain name. Besides lowercase letters, these emails may contain ‘.’s or ‘+’s.
If you add periods (‘.’) between some characters in the local name part of an email address, mail sent there will be forwarded to the same address without dots in the local name. For example, “alice.z@leetcode.com” and “alicez@leetcode.com” forward to the same email address. (Note that this rule does not apply for domain names.)
If you add a plus (‘+’) in the local name, everything after the first plus sign will be ignored. This allows certain emails to be filtered, for example m.y+name@email.com will be forwarded to my@email.com. (Again, this rule does not apply for domain names.)
It is possible to use both of these rules at the same time.
Given a list of emails, we send one email to each address in the list. How many different addresses actually receive mails?
解析: For each email address, convert it to the canonical address that actually receives the mail. This involves a few steps:
Separate the email address into a local part and the rest of the address.
If the local part has a ‘+’ character, remove it and everything beyond it from the local part.
Remove all the zeros from the local part.
The canonical address is local + rest.
After, we can count the number of unique canonical addresses with a Set structure.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
classSolution { publicintnumUniqueEmails(String[] emails){ Set<String> seen = newHashSet(); for (String email: emails) { int i = email.indexOf('@'); String local = email.substring(0, i); String rest = email.substring(i); if (local.contains("+")) { local = local.substring(0, local.indexOf('+')); } local = local.replaceAll(".", ""); seen.add(local + rest); }
return seen.size(); } }
Leetcode930. Binary Subarrays With Sum
In an array A of 0s and 1s, how many non-empty subarrays have sum S?
Example 1:
1 2 3 4 5 6 7 8
Input: A = [1,0,1,0,1], S = 2 Output: 4 Explanation: The 4 subarrays are bolded below: [1,0,1] [1,0,1,0] [0,1,0,1] [1,0,1]
Note:
A.length <= 30000
0 <= S <= A.length
A[i] is either 0 or 1.
这道题给了我们一个只由0和1组成的数组A,还有一个整数S,问数组A中有多少个子数组使得其和正好为S。博主最先没看清题意,以为是按二进制数算的,但是看了例子之后才发现,其实只是单纯的求和而已。那么马上就想着应该是要建立累加和数组的,然后遍历所有的子数组之和,但是这个遍历的过程还是平方级的复杂度,这道题的 OJ 卡的比较严格,只放行线性的时间复杂度。所以这种遍历方式是不行的,但仍需要利用累加和的思路,具体的方法是在遍历的过程中使用一个变量 curSum 来记录当前的累加和,同时使用一个 HashMap,用来映射某个累加出现的次数,初始化需要放入 {0,1} 这个映射对儿,后面会讲解原因。在遍历数组的A的时候,对于每个遇到的数字 num,都加入累加和 curSum 中,然后看若 curSum-S 这个数有映射值的话,那么说明就存在 m[curSum-S] 个符合题意的子数组,应该加入到结果 res 中,假如 curSum 正好等于S,即 curSum-S=0 的时候,此时说明从开头到当前位置正好是符合题目要求的子数组,现在明白刚开始为啥要加入 {0,1} 这个映射对儿了吧,就是为了处理这种情况。然后此时 curSum 的映射值自增1即可。其实这道题的解法思路跟之前那道 Contiguous Array 是一样的,那道题是让找0和1个数相同的子数组,这里让找和为S的子数组,都可以用一个套路来解题,参见代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
classSolution { public: intnumSubarraysWithSum(vector<int>& A, int S){ int res = 0, curSum = 0; unordered_map<int, int> m{{0, 1}}; for (int num : A) { curSum += num; res += m[curSum - S]; ++m[curSum]; } return res; } };
我们也可以使用滑动窗口 Sliding Window 来做,也是线性的时间复杂度,其实还是利用到了累计和的思想,不过这个累加和不是从开头到当前位置之和,而是这个滑动窗口内数字之和,这 make sense 吧,因为只要这个滑动窗口内数字之和正好等于S了,即是符合题意的一个子数组。遍历数组A,将当前数字加入 sum 中,然后看假如此时 sum 大于S了,则要进行收缩窗口操作,左边界 left 右移,并且 sum 要减去这个移出窗口的数字,当循环退出后,假如此时 sum 小于S了,说明当前没有子数组之和正好等于S,若 sum 等于S了,则结果 res 自增1。此时还需要考虑一种情况,就是当窗口左边有连续0的时候,因为0并不影响 sum,但是却要算作不同的子数组,所以要统计左起连续0的个数,并且加到结果 res 中即可,参见代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
classSolution { public: intnumSubarraysWithSum(vector<int>& A, int S){ int res = 0, sum = 0, left = 0, n = A.size(); for (int i = 0; i < n; ++i) { sum += A[i]; while (left < i && sum > S) sum -= A[left++]; if (sum < S) continue; if (sum == S) ++res; for (int j = left; j < i && A[j] == 0; ++j) { ++res; } } return res; } };
Leetcode931. Minimum Falling Path Sum
Given a square array of integers A, we want the minimum sum of a falling path through A.
A falling path starts at any element in the first row, and chooses one element from each row. The next row’s choice must be in a column that is different from the previous row’s column by at most one.
Example 1:
1 2 3 4 5 6 7 8 9
Input: [[1,2,3],[4,5,6],[7,8,9]] Output: 12 Explanation: The possible falling paths are:
- `[1,4,7], [1,4,8], [1,5,7], [1,5,8], [1,5,9]` - `[2,4,7], [2,4,8], [2,5,7], [2,5,8], [2,5,9], [2,6,8], [2,6,9]` - `[3,5,7], [3,5,8], [3,5,9], [3,6,8], [3,6,9]` The falling path with the smallest sum is [1,4,7], so the answer is 12.
classSolution { public: intminFallingPathSum(vector<vector<int>>& A){ if (A.size() == 1) return A[0][0]; int n = A.size(), res = INT_MAX; for (int i = 1; i < n; ++i) { for (int j = 0; j < n; ++j) { int pre = A[i - 1][j]; if (j > 0) pre = min(pre, A[i - 1][j - 1]); if (j < n - 1) pre = min(pre, A[i - 1][j + 1]); A[i][j] += pre; if (i == n - 1) res = min(res, A[i][j]); } } return res; } };
Leetcode932. Beautiful Array
For some fixed N, an array A is beautiful if it is a permutation of the integers 1, 2, …, N, such that:
For every i < j, there is no k with i < k < j such that A[k] * 2 = A[i] + A[j].
Given N, return any beautiful array A. (It is guaranteed that one exists.)
classSolution { public: vector<int> beautifulArray(int N){ vector<int> res{1}; while (res.size() < N) { vector<int> t; for (int num : res) { if (num * 2 - 1 <= N) t.push_back(num * 2 - 1); } for (int num : res) { if (num * 2 <= N) t.push_back(num * 2); } res = t; } return res; } };
Leetcode933. Number of Recent Calls
Write a class RecentCounter to count recent requests. It has only one method: ping(int t), where t represents some time in milliseconds. Return the number of pings that have been made from 3000 milliseconds ago until now. Any ping with time in [t - 3000, t] will count, including the current ping. It is guaranteed that every call to ping uses a strictly larger value of t than before.
classSolution { public: intshortestBridge(vector<vector>& A){ int res = 0, n = A.size(), startX = -1, startY = -1; queue<int> q; vector<int> dirX{-1, 0, 1, 0}, dirY = {0, 1, 0, -1}; for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { if (A[i][j] == 0) continue; startX = i; startY = j; break; } if (startX != -1) break; } helper(A, startX, startY, q); while (!q.empty()) { for (int i = q.size(); i > 0; --i) { int t = q.front(); q.pop(); for (int k = 0; k < 4; ++k) { int x = t / n + dirX[k], y = t % n + dirY[k]; if (x < 0 || x >= n || y < 0 || y >= n || A[x][y] == 2) continue; if (A[x][y] == 1) return res; A[x][y] = 2; q.push(x * n + y); } } ++res; } return res; } voidhelper(vector<vector>& A, int x, int y, queue& q){ int n = A.size(); if (x < 0 || x >= n || y < 0 || y >= n || A[x][y] == 0 || A[x][y] == 2) return; A[x][y] = 2; q.push(x * n + y); helper(A, x + 1, y, q); helper(A, x, y + 1, q); helper(A, x - 1, y, q); helper(A, x, y - 1, q); } };
``C++ classSolution { public: intshortestBridge(vector<vector>& A){ int res = 0, n = A.size(); queue<int> q, que; vector<int> dirX{-1, 0, 1, 0}, dirY = {0, 1, 0, -1}; for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { if (A[i][j] == 0) continue; A[i][j] = 2; que.push(i * n + j); break; } if (!que.empty()) break; } while (!que.empty()) { int t = que.front(); que.pop(); q.push(t); for (int k = 0; k < 4; ++k) { int x = t / n + dirX[k], y = t % n + dirY[k]; if (x < 0 || x >= n || y < 0 || y >= n || A[x][y] == 0 || A[x][y] == 2) continue; A[x][y] = 2; que.push(x * n + y); } } while (!q.empty()) { for (int i = q.size(); i > 0; --i) { int t = q.front(); q.pop(); for (int k = 0; k < 4; ++k) { int x = t / n + dirX[k], y = t % n + dirY[k]; if (x < 0 || x >= n || y < 0 || y >= n || A[x][y] == 2) continue; if (A[x][y] == 1) return res; A[x][y] = 2; q.push(x * n + y); } } ++res; } return res; } };
LeetCode] 935. Knight Dialer 骑士拨号器
The chess knight has a unique movement, it may move two squares vertically and one square horizontally, or two squares horizontally and one square vertically (with both forming the shape of an L). The possible movements of chess knight are shown in this diagaram:
A chess knight can move as indicated in the chess diagram below:
We have a chess knight and a phone pad as shown below, the knight can only stand on a numeric cell (i.e. blue cell).
Given an integer n, return how many distinct phone numbers of length n we can dial.
You are allowed to place the knight on any numeric cell initially and then you should perform n - 1 jumps to dial a number of length n. All jumps should be valid knight jumps.
As the answer may be very large, return the answer modulo 109 + 7.
Example 1:
1 2 3
Input: n = 1 Output: 10 Explanation: We need to dial a number of length 1, so placing the knight over any numeric cell of the 10 cells is sufficient.
Example 2:
1 2 3
Input: n = 2 Output: 20 Explanation: All the valid number we can dial are [04, 06, 16, 18, 27, 29, 34, 38, 40, 43, 49, 60, 61, 67, 72, 76, 81, 83, 92, 94]
Example 3:
1 2
Input: n = 3 Output: 46
Example 4:
1 2
Input: n = 4 Output: 104
Example 5:
1 2 3
Input: n = 3131 Output: 136006598 Explanation: Please take care of the mod.
classSolution { public: intknightDialer(int N){ int res = 0, M = 1e9 + 7; vector<vector<int>> memo(N + 1, vector<int>(10)); vector<vector<int>> path{{4, 6}, {6, 8}, {7, 9}, {4, 8}, {3, 9, 0}, {}, {1, 7, 0}, {2, 6}, {1, 9}, {4, 2}}; for (int i = 0; i < 10; ++i) { res = (res + helper(N - 1, i, path, memo)) % M; } return res; } inthelper(int n, int cur, vector<vector<int>>& path, vector<vector<int>>& memo){ if (n == 0) return1; if (memo[n][cur] != 0) return memo[n][cur]; int res = 0, M = 1e9 + 7; for (int idx : path[cur]) { res = (res + helper(n - 1, idx, path, memo)) % M; } return memo[n][cur] = res; } };
Leetcode937. Reorder Data in Log Files
You have an array of logs. Each log is a space delimited string of words.
For each log, the first word in each log is an alphanumeric identifier. Then, either:
Each word after the identifier will consist only of lowercase letters, or;
Each word after the identifier will consist only of digits. We will call these two varieties of logs letter-logs and digit-logs. It is guaranteed that each log has at least one word after its identifier.
Reorder the logs so that all of the letter-logs come before any digit-log. The letter-logs are ordered lexicographically ignoring identifier, with the identifier used in case of ties. The digit-logs should be put in their original order.
Return the final order of the logs.
Example 1:
1 2
Input: logs = ["dig1 8 1 5 1","let1 art can","dig2 3 6","let2 own kit dig","let3 art zero"] Output: ["let1 art can","let3 art zero","let2 own kit dig","dig1 8 1 5 1","dig2 3 6"]
We are given an array A of N lowercase letter strings, all of the same length.
Now, we may choose any set of deletion indices, and for each string, we delete all the characters in those indices.
For example, if we have an array A = [“abcdef”,”uvwxyz”] and deletion indices {0, 2, 3}, then the final array after deletions is [“bef”, “vyz”], and the remaining columns of A are [“b”,”v”], [“e”,”y”], and [“f”,”z”]. (Formally, the c-th column is [A[0][c], A[1][c], …, A[A.length-1][c]].)
Suppose we chose a set of deletion indices D such that after deletions, each remaining column in A is in non-decreasing sorted order.
Return the minimum possible value of D.length.
Example 1:
1 2
Input: ["cba","daf","ghi"] Output: 1
Explanation: After choosing D = {1}, each column [“c”,”d”,”g”] and [“a”,”f”,”i”] are in non-decreasing sorted order. If we chose D = {}, then a column [“b”,”a”,”h”] would not be in non-decreasing sorted order. Example 2:
1 2
Input: ["a","b"] Output: 0
Explanation: D = {} Example 3:
1 2
Input: ["zyx","wvu","tsr"] Output: 3
Explanation: D = {0, 1, 2}
Note:
1 <= A.length <= 100 1 <= A[i].length <= 1000
字符串数组 A 中的每个字符串元素的长度相同,统计index个数,这个index 的要求是 A[i].charAt(index),i=0,1,2,3,4 组成的 字符序列 不是严格递增。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
classSolution { public: intminDeletionSize(vector<string>& A){ int isize=A.size(); int jsize=A[0].size(); int ans=0; for(int j=0;j<jsize;j++){ for(int i=0;i<isize-1;i++){ if(A[i][j]>A[i+1][j]){ ans++; break; } } } return ans; } };
Leetcode945. Minimum Increment to Make Array Unique
Given an array of integers A, a move consists of choosing any A[i], and incrementing it by 1.
Return the least number of moves to make every value in A unique.
Example 1:
1 2 3
Input: [1,2,2] Output: 1 Explanation: After 1 move, the array could be [1, 2, 3].
Example 2:
1 2 3 4
Input: [3,2,1,2,1,7] Output: 6 Explanation: After 6 moves, the array could be [3, 4, 1, 2, 5, 7]. It can be shown with 5 or less moves that it is impossible for the array to have all unique values.
Note:
0 <= A.length <= 40000
0 <= A[i] < 40000
这道题给了一个数组,说是每次可以将其中一个数字增加1,问最少增加多少次可以使得数组中没有重复数字。给的两个例子可以帮助我们很好的理解题意,这里主要参考了 lee215 大神的帖子,假如数组中没有重复数字的话,则不需要增加,只有当重复数字存在的时候才需要增加。比如例子1中,有两个2,需要将其中一个2增加到3,才能各不相同。但有时候只增加一次可能并不能解决问题,比如例子2中,假如要处理两个1,增加其中一个到2并不能解决问题,因此2也是有重复的,甚至增加到3还是有重复,所以一直得增加到4才行,但此时如何知道后面是否还有1,所以需要一个统一的方法来增加,最好是能从小到大处理数据,则先给数组排个序,然后用一个变量 need 表示此时需要增加到的数字,初始化为0,由于是从小到大处理,这个 need 会一直变大,而且任何小于 need 的数要么是数组中的数,要么是某个数字增后的数,反正都是出现过了。然后开始遍历数组,对于遍历到的数字 num,假如 need 大于 num,说明此时的 num 是重复数字,必须要提高到 need,则将 need-num 加入结果 res 中,反之若 need 小于 num,说明 num 并未出现过,不用增加。然后此时更新 need 为其和 num 之间的较大值并加1,因为 need 不能有重复,所以要加1,参见代码如下:
1 2 3 4 5 6 7 8 9 10 11 12
classSolution { public: intminIncrementForUnique(vector<int>& A){ int res = 0, need = 0; sort(A.begin(), A.end()); for (int num : A) { res += max(need - num, 0); need = max(num, need) + 1; } return res; } };
classSolution { public: intminIncrementForUnique(vector<int>& A){ int res = 0, need = 0; map<int, int> numCnt; for (int num : A) ++numCnt[num]; for (auto &a : numCnt) { res += a.second * max(need - a.first, 0) + a.second * (a.second - 1) / 2; need = max(need, a.first) + a.second; } return res; } };
再来看一种联合查找 Union Find 的方法,这是一种并查集的方法,在岛屿群组类的问题上很常见,可以搜搜博主之前关于岛屿类题目的讲解,很多都使用了这种方法。但是这道题乍一看好像跟群组并没有明显的关系,但其实是有些很微妙的联系的。这里的 root 使用一个 HashMap,而不是用数组,因为数字不一定是连续的,而且可能跨度很大,使用 HashMap 会更加省空间一些。遍历原数组,对于每个遍历到的数字 num,调用 find 函数,这里实际上就是查找上面的方法中的 need,即最小的那个不重复的新数字,而 find 函数中会不停的更新 root[x],而只要x存在,则不停的自增1,直到不存在时候,则返回其本身,那么实际上从 num 到 need 中所有的数字的 root 值都标记成了 need,就跟它们是属于一个群组一样,这样做的好处在以后的查询过程中可以更快的找到 need 值,这也是为啥这种方法不用给数组排序的原因,若还是不理解的童鞋可以将例子2代入算法一步一步执行,看每一步的 root 数组的值是多少,应该不难理解,参见代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
classSolution { public: intminIncrementForUnique(vector<int>& A){ int res = 0; unordered_map<int, int> root; for (int num : A) { res += find(root, num) - num; } return res; } intfind(unordered_map<int, int>& root, int x){ return root[x] = root.count(x) ? find(root, root[x] + 1) : x; } };
LeetCode946. Validate Stack Sequences
Given two sequences pushed and popped with distinct values, return true if and only if this could have been the result of a sequence of push and pop operations on an initially empty stack.
Example 1:
1 2 3 4 5
Input: pushed = [1,2,3,4,5], popped = [4,5,3,2,1] Output: true Explanation: We might do the following sequence: push(1), push(2), push(3), push(4), pop() -> 4, push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
Example 2:
1 2 3
Input: pushed = [1,2,3,4,5], popped = [4,3,5,1,2] Output: false Explanation: 1 cannot be popped before 2.
Note:
0 <= pushed.length == popped.length <= 1000
0 <= pushed[i], popped[i] < 1000
pushed is a permutation of popped.
pushed and popped have distinct values.
这道题给了两个序列 pushed 和 popped,让判断这两个序列是否能表示同一个栈的压入和弹出操作,由于栈是后入先出的顺序,所以并不是任意的两个序列都是满足要求的。比如例子2中,先将 1,2,3,4 按顺序压入栈,此时4和3出栈,接下来压入5,再让5出栈,接下来出栈的是2而不是1,所以例子2会返回 false。而这道题主要就是模拟这个过程,使用一个栈,和一个变量i用来记录弹出序列的当前位置,此时遍历压入序列,对遍历到的数字都压入栈,此时要看弹出序列当前的数字是否和栈顶元素相同,相同的话就需要移除栈顶元素,并且i自增1,若下一个栈顶元素还跟新位置上的数字相同,还要进行相同的操作,所以用一个 while 循环来处理。直到最终遍历完压入序列后,若此时栈为空,则说明是符合题意的,否则就是 false,参见代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
classSolution { public: boolvalidateStackSequences(vector<int>& pushed, vector<int>& popped){ stack<int> st; int i = 0; for (int num : pushed) { st.push(num); while (!st.empty() && st.top() == popped[i]) { st.pop(); ++i; } } return st.empty(); } };
Leetcode947. Most Stones Removed with Same Row or Column
On a 2D plane, we place n stones at some integer coordinate points. Each coordinate point may have at most one stone.
A stone can be removed if it shares either the same row or the same column as another stone that has not been removed.
Given an array stones of length n where stones[i] = [xi, yi] represents the location of the ith stone, return the largest possible number of stones that can be removed.
Example 1:
1 2 3 4 5 6 7 8 9
Input: stones = [[0,0],[0,1],[1,0],[1,2],[2,1],[2,2]] Output: 5 Explanation: One way to remove 5 stones is as follows: Remove stone [2,2] because it shares the same row as [2,1]. Remove stone [2,1] because it shares the same column as [0,1]. Remove stone [1,2] because it shares the same row as [1,0]. Remove stone [1,0] because it shares the same column as [0,0]. Remove stone [0,1] because it shares the same row as [0,0]. Stone [0,0] cannot be removed since it does not share a row/column with another stone still on the plane.
classSolution { public: intbagOfTokensScore(vector<int>& tokens, int P){ int res = 0, cur = 0, n = tokens.size(), i = 0, j = n - 1; sort(tokens.begin(), tokens.end()); while (i <= j) { while (i <= j && tokens[i] <= P) { P -= tokens[i++]; res = max(res, ++cur); } if (i > j || cur == 0) break; --cur; P += tokens[j--]; } return res; } };
我们也可以换一种写法,不用 while 套 while,而是换成赏心悦目的 if … else 语句,其实也没差啦,参见代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
classSolution { public: intbagOfTokensScore(vector<int>& tokens, int P){ int res = 0, cur = 0, n = tokens.size(), i = 0, j = n - 1; sort(tokens.begin(), tokens.end()); while (i <= j) { if (P >= tokens[i]) { P -= tokens[i++]; res = max(res, ++cur); } elseif (cur > 0) { --cur; P += tokens[j--]; } else { break; } } return res; } };
classSolution { public: intbagOfTokensScore(vector<int>& tokens, int P){ sort(tokens.begin(), tokens.end()); returnhelper(tokens, P, 0, (int)tokens.size() - 1, 0); } inthelper(vector<int>& tokens, int P, int i, int j, int cur){ if (i > j) return cur; int res = cur; if (tokens[i] <= P) { res = max(res, helper(tokens, P - tokens[i], i + 1, j, cur + 1)); } elseif (cur > 0) { res = max(res, helper(tokens, P + tokens[j], i, j - 1, cur - 1)); } return res; } };
Leetcode949. Largest Time for Given Digits
Given an array of 4 digits, return the largest 24 hour time that can be made. The smallest 24 hour time is 00:00, and the largest is 23:59. Starting from 00:00, a time is larger if more time has elapsed since midnight. Return the answer as a string of length 5. If no valid time can be made, return an empty string.
In a deck of cards, every card has a unique integer. You can order the deck in any order you want.
Initially, all the cards start face down (unrevealed) in one deck.
Now, you do the following steps repeatedly, until all cards are revealed:
Take the top card of the deck, reveal it, and take it out of the deck. If there are still cards in the deck, put the next top card of the deck at the bottom of the deck. If there are still unrevealed cards, go back to step 1. Otherwise, stop. Return an ordering of the deck that would reveal the cards in increasing order.
The first entry in the answer is considered to be the top of the deck.
Example 1:
1 2 3 4 5 6 7 8 9 10 11 12 13
Input: [17,13,11,2,3,5,7] Output: [2,13,3,11,5,17,7] Explanation: We get the deck in the order [17,13,11,2,3,5,7] (this order doesn't matter), and reorder it. After reordering, the deck starts as [2,13,3,11,5,17,7], where 2 is the top of the deck. We reveal 2, and move 13 to the bottom. The deck is now [3,11,5,17,7,13]. We reveal 3, and move 11 to the bottom. The deck is now [5,17,7,13,11]. We reveal 5, and move 17 to the bottom. The deck is now [7,13,11,17]. We reveal 7, and move 13 to the bottom. The deck is now [11,17,13]. We reveal 11, and move 17 to the bottom. The deck is now [13,17]. We reveal 13, and move 17 to the bottom. The deck is now [17]. We reveal 17. Since all the cards revealed are in increasing order, the answer is correct.
Note:
1 <= A.length <= 1000 1 <= A[i] <= 10^6 A[i] != A[j] for all i != j
For a binary tree T, we can define a flip operation as follows: choose any node, and swap the left and right child subtrees.
A binary tree X is flip equivalent to a binary tree Y if and only if we can make X equal to Y after some number of flip operations.
Write a function that determines whether two binary trees are flip equivalent. The trees are given by root nodes root1 and root2.
Example 1:
Input: root1 = [1,2,3,4,5,6,null,null,null,7,8], root2 = [1,3,2,null,6,4,5,null,null,null,null,8,7] Output: true Explanation: We flipped at nodes with values 1, 3, and 5.
classSolution { public: boolflipEquiv(TreeNode* root1, TreeNode* root2){ // Two null trees are flip equivalent // A non-null and null tree are NOT flip equivalent // Two non-null trees with different root values are NOT flip equivalent // Two non-null trees are flip equivalent if // The left subtree of tree1 is flip equivalent with the left subtree of tree2 and the right subtree of tree1 is // flipequivalent with the right subtree of tree2 (no flip case) // OR // The right subtree of tree1 is flip equivalent with the left subtree of tree2 and the left subtree of tree1 is // flipequivalent with the right subtree of tree2 (flip case) if ( !root1 && !root2 ) returntrue; if ( !root1 && root2 || root1 &&!root2 || root1->val != root2->val ) returnfalse; returnflipEquiv( root1->left, root2->left ) && flipEquiv( root1->right, root2->right ) || flipEquiv( root1->right, root2->left ) && flipEquiv( root1->left, root2->right ); } };
Leetcode953. Verifying an Alien Dictionary
In an alien language, surprisingly they also use english lowercase letters, but possibly in a different order. The order of the alphabet is some permutation of lowercase letters.
Given a sequence of words written in the alien language, and the order of the alphabet, return true if and only if the given words are sorted lexicographicaly in this alien language.
Example 1:
1 2 3
Input: words = ["hello","leetcode"], order = "hlabcdefgijkmnopqrstuvwxyz" Output: true Explanation: As 'h' comes before 'l' in this language, then the sequence is sorted.
Example 2:
1 2 3
Input: words = ["word","world","row"], order = "worldabcefghijkmnpqstuvxyz" Output: false Explanation: As 'd' comes after 'l' in this language, then words[0] > words[1], hence the sequence is unsorted.
Example 3:
1 2 3
Input: words = ["apple","app"], order = "abcdefghijklmnopqrstuvwxyz" Output: false Explanation: The first three characters "app" match, and the second string is shorter (in size.) According to lexicographical rules "apple" > "app", because 'l' > '∅', where '∅' is defined as the blank character which is less than any other character (More info).
classSolution { public: boolisAlienSorted(vector<string>& words, string order){ map<char, int> mp; for(int i = 0; i < order.length(); i ++) mp[order[i]] = i; int size = words.size(); for(int i = 0; i < size-1; i ++) { int j = i + 1; int min_size = min(words[i].length(), words[j].length()); int k; for(k = 0; k < min_size; k ++) if(mp[words[i][k]] > mp[words[j][k]]) returnfalse; elseif (mp[words[i][k]] < mp[words[j][k]]) break; if(k == min_size && words[i].length() > words[j].length()) returnfalse; } returntrue; } };
Leetcode955. Delete Columns to Make Sorted II
We are given an array A of N lowercase letter strings, all of the same length.
Now, we may choose any set of deletion indices, and for each string, we delete all the characters in those indices.
For example, if we have an array A = [“abcdef”,”uvwxyz”] and deletion indices {0, 2, 3}, then the final array after deletions is [“bef”,”vyz”].
Suppose we chose a set of deletion indices D such that after deletions, the final array has its elements in lexicographic order (A[0] <= A[1] <= A[2] … <= A[A.length - 1]).
Return the minimum possible value of D.length.
Example 1:
1 2 3 4 5 6
Input: ["ca","bb","ac"] Output: 1 Explanation: After deleting the first column, A = ["a", "b", "c"]. Now A is in lexicographic order (ie. A[0] <= A[1] <= A[2]). We require at least 1 deletion since initially A was not in lexicographic order, so the answer is 1.
Example 2:
1 2 3 4 5 6
Input: ["xc","yb","za"] Output: 0 Explanation: A is already in lexicographic order, so we don't need to delete anything. Note that the rows of A are not necessarily in lexicographic order: ie. it is NOT necessarily true that (A[0][0] <= A[0][1] <= ...)
Example 3:
1 2 3 4
Input: ["zyx","wvu","tsr"] Output: 3 Explanation: We have to delete every column.
Note:
1 <= A.length <= 100
1 <= A[i].length <= 100
这道题说是给了一个字符串数组,里面的字符串长度均相同,这样如果将每个字符串看作一个字符数组的话,于是就可以看作的一个二维数组,题目要求数组中的字符串是按照字母顺序的,问最少需要删掉多少列。我们知道比较两个长度相等的字符串的字母顺序时,就是从开头起按照两两对应的位置比较,只要前面的字符顺序已经比出来了,后面的字符的顺序就不用管了,比如 “bx” 和 “ea”,因为 b 比 e 小,所以 “bx” 比 “ea” 小,后面的 x 和 a 的顺序无关紧要。如果看成二维数组的话,在比较A[i][j]和A[i+1][j]时,假如 [0, j-1] 中的某个位置k,已经满足了A[i][k] < A[i+1][k]的话,这里就不用再比了,所以用一个数组 sorted 来标记某相邻的两个字符串之间是否已经按照字母顺序排列了。然后用两个 for 循环,外层是遍历列,内层是遍历行,然后看若sorted[i]为 false,且A[i][j] > A[i + 1][j]的话,说明当前列需要被删除,结果 res 自增1,且 break 掉内层 for 循环。当内层 for 循环 break 掉或者自己结束后,此时看 i 是否小于 m-1,是的话说明是 break 掉的,直接 continue 外层循环。若是自己退出的,则在遍历一遍所有行,更新一下 sorted 数组即可,参见代码如下:
classSolution { public: intminDeletionSize(vector<string>& A){ int res = 0, m = A.size(), n = A[0].size(), i = 0, j = 0; vector<int> sorted(m - 1); for (j = 0; j < n; ++j) { for (i = 0; i < m - 1; ++i) { if (!sorted[i] && A[i][j] > A[i + 1][j]) { ++res; break; } } if (i < m - 1) continue; for (i = 0; i < m - 1; ++i) { sorted[i] |= A[i][j] < A[i + 1][j]; } } return res; } };
Leetcode957. Prison Cells After N Days
There are 8 prison cells in a row, and each cell is either occupied or vacant.
Each day, whether the cell is occupied or vacant changes according to the following rules:
If a cell has two adjacent neighbors that are both occupied or both vacant, then the cell becomes occupied. Otherwise, it becomes vacant. (Note that because the prison is a row, the first and the last cells in the row can’t have two adjacent neighbors.)
We describe the current state of the prison in the following way: cells[i] == 1 if the i-th cell is occupied, else cells[i] == 0.
Given the initial state of the prison, return the state of the prison after N days (and N such changes described above.)
Example 1:
1 2 3 4 5 6 7 8 9 10 11
Input: cells = [0,1,0,1,1,0,0,1], N = 7 Output: [0,0,1,1,0,0,0,0] Explanation: The following table summarizes the state of the prison on each day: Day 0: [0, 1, 0, 1, 1, 0, 0, 1] Day 1: [0, 1, 1, 0, 0, 0, 0, 0] Day 2: [0, 0, 0, 0, 1, 1, 1, 0] Day 3: [0, 1, 1, 0, 0, 1, 0, 0] Day 4: [0, 0, 0, 0, 0, 1, 0, 0] Day 5: [0, 1, 1, 1, 0, 1, 0, 0] Day 6: [0, 0, 1, 0, 1, 1, 0, 0] Day 7: [0, 0, 1, 1, 0, 0, 0, 0]
Example 2:
1 2
Input: cells = [1,0,0,1,0,0,1,0], N = 1000000000 Output: [0,0,1,1,1,1,1,0]
classSolution { public: vector<int> prisonAfterNDays(vector<int>& cells, int N){ vector<int> res; string str; for (int num : cells) str += to_string(num); unordered_map<string, int> m; while (N > 0) { m[str] = N--; string cur(8, '0'); for (int i = 1; i < 7; ++i) { cur[i] = (str[i - 1] == str[i + 1]) ? '1' : '0'; } str = cur; if (m.count(str)) { N %= m[str] - N; } } for (char c : str) res.push_back(c - '0'); return res; } };
Leetcode958. Check Completeness of a Binary Tree
Given a binary tree, determine if it is a complete binary tree.
Definition of a complete binary tree from Wikipedia: In a complete binary tree every level, except possibly the last, is completely filled, and all nodes in the last level are as far left as possible. It can have between 1 and 2h nodes inclusive at the last level h.
Example 1:
1 2 3
Input: [1,2,3,4,5,6] Output: true Explanation: Every level before the last is full (ie. levels with node-values {1} and {2, 3}), and all nodes in the last level ({4, 5, 6}) are as far left as possible.
Example 2:
1 2 3
Input: [1,2,3,4,5,null,7] Output: false Explanation: The node with value 7 isn't as far left as possible.
In a N x N grid composed of 1 x 1 squares, each 1 x 1 square consists of a /, \, or blank space. These characters divide the square into contiguous regions.
(Note that backslash characters are escaped, so a \ is represented as “\\”.)
Return the number of regions.
Example 1:
1 2 3 4 5
Input: [ " /", "/ " ] Output: 2
Explanation: The 2x2 grid is as follows:
Example 2:
1 2 3 4 5
Input: [ " /", " " ] Output: 1
Explanation: The 2x2 grid is as follows:
Example 3:
1 2 3 4 5 6
Input: [ "\\/", "/\\" ] Output: 4 Explanation: (Recall that because \ characters are escaped, "\\/" refers to \/, and "/\\" refers to /\.)
The 2x2 grid is as follows:
Example 4:
1 2 3 4 5 6
Input: [ "/\\", "\\/" ] Output: 5 Explanation: (Recall that because \ characters are escaped, "/\\" refers to /\, and "\\/" refers to \/.)
The 2x2 grid is as follows:
Example 5:
1 2 3 4 5 6
Input: [ "//", "/ " ] Output: 3 Explanation: The 2x2 grid is as follows:
Note:
1 <= grid.length == grid[0].length <= 30
grid[i][j] is either ‘/‘, ‘\’, or ‘ ‘.
这道题说是有个 NxN 个小方块,每个小方块里可能是斜杠,反斜杠,或者是空格。然后问这些斜杠能将整个区域划分成多少个小区域。这的确是一道很有意思的题目,虽然只是 Medium 的难度,但是博主拿到题目的时候是懵逼的,这尼玛怎么做?无奈只好去论坛上看大神们的解法,结果发现大神们果然牛b,巧妙的将这道题转化为了岛屿个数问题 Number of Islands,具体的做法将每个小区间化为九个小格子,这样斜杠或者反斜杠就是对角线或者逆对角线了,是不是有点图像像素化的感觉,就是当你把某个图片尽可能的放大后,到最后你看到也就是一个个不同颜色的小格子组成了这幅图片。这样只要把斜杠的位置都标记为1,而空白的位置都标记为0,这样只要找出分隔开的0的群组的个数就可以了,就是岛屿个数的问题啦。使用一个 DFS 来遍历即可,这个并不难,这道题难就难在需要想出来这种像素化得转化,确实需要灵光一现啊,参见代码如下:
classSolution { public: intregionsBySlashes(vector<string>& grid){ int n = grid.size(), res = 0; vector<vector<int>> nums(3 * n, vector<int>(3 * n)); for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { if (grid[i][j] == '/') { nums[i * 3][j * 3 + 2] = 1; nums[i * 3 + 1][j * 3 + 1] = 1; nums[i * 3 + 2][j * 3] = 1; } elseif (grid[i][j] == '\\') { nums[i * 3][j * 3] = 1; nums[i * 3 + 1][j * 3 + 1] = 1; nums[i * 3 + 2][j * 3 + 2] = 1; } } } for (int i = 0; i < nums.size(); ++i) { for (int j = 0; j < nums.size(); ++j) { if (nums[i][j] == 0) { helper(nums, i, j); ++res; } } } return res; } voidhelper(vector<vector<int>>& nums, int i, int j){ if (i >= 0 && j >= 0 && i < nums.size() && j < nums.size() && nums[i][j] == 0) { nums[i][j] = 1; helper(nums, i - 1, j); helper(nums, i, j + 1); helper(nums, i + 1, j); helper(nums, i, j - 1); } } };
Leetcode961. N-Repeated Element in Size 2N Array
In a array A of size 2N, there are N+1 unique elements, and exactly one of these elements is repeated N times.
Return the element repeated N times.
Example 1:
1 2
Input: [1,2,3,3] Output: 3
Example 2:
1 2
Input: [2,1,2,5,3,2] Output: 2
Example 3:
1 2
Input: [5,1,5,2,5,3,5,4] Output: 5
Note:
4 <= A.length <= 10000 0 <= A[i] < 10000 A.length is even
一个桶排序搞定
1 2 3 4 5 6 7 8 9 10 11 12 13
classSolution { public: intrepeatedNTimes(vector<int>& A){ int counter[10000]; memset(counter, 0, sizeof(counter)); for(int i = 0; i < A.size(); i ++){ counter[A[i]] ++; if(counter[A[i]] >= A.size() / 2) return A[i]; } return-1; } };
看到了大佬的解法,跪了,如果有两个连续一样的元素,直接返回
The intuition here is that the repeated numbers have to appear either next to each other (A[i] == A[i + 1]), or alternated (A[i] == A[i + 2]).
The only exception is sequences like [2, 1, 3, 2]. In this case, the result is the last number, so we just return it in the end. This solution has O(n) runtime.
1 2 3 4 5
int repeatedNTimes(vector<int>& A) { for (auto i = 0; i < A.size() - 2; ++i) if (A[i] == A[i + 1] || A[i] == A[i + 2]) return A[i]; return A[A.size() - 1]; }
Another interesting approach is to use randomization (courtesy of @lee215 ). If you pick two numbers randomly, there is a 25% chance you bump into the repeated number. So, in average, we will find the answer in 4 attempts, thus O(4) runtime.
1 2 3 4
int repeatedNTimes(vector<int>& A, int i = 0, int j = 0) { while (A[i = rand() % A.size()] != A[j = rand() % A.size()] || i == j); return A[i]; }
Leetcode962. Maximum Width Ramp
A ramp in an integer array nums is a pair (i, j) for which i < j and nums[i] <= nums[j]. The width of such a ramp is j - i.
Given an integer array nums, return the maximum width of a ramp in nums. If there is no ramp in nums, return 0.
Example 1:
1 2 3
Input: nums = [6,0,8,2,1,5] Output: 4 Explanation: The maximum width ramp is achieved at (i, j) = (1, 5): nums[1] = 0 and nums[5] = 5.
Example 2:
1 2 3
Input: nums = [9,8,1,0,1,9,4,0,4,1] Output: 7 Explanation: The maximum width ramp is achieved at (i, j) = (2, 9): nums[2] = 1 and nums[9] = 1.
classSolution { public: intmaxWidthRamp(vector<int>& nums){ int res = 0; vector<int> s; for (int i = 0; i < nums.size(); i ++) { if (s.size() == 0 || nums[i] <= nums[s.back()]) s.push_back(i); else { int left = 0, right = s.size()-1; while(left < right) { int mid = left + (right - left) / 2; if (nums[mid] > nums[i]) left = mid+1; else right = mid; } res = max(res, i - s[right]); } } return res; } };
Given a set of points in the xy-plane, determine the minimum area of any rectangle formed from these points, with sides not necessarily parallel to the x and y axes.
If there isn’t any rectangle, return 0.
Example 1:
1 2 3
Input: [[1,2],[2,1],[1,0],[0,1]] Output: 2.00000 Explanation: The minimum area rectangle occurs at [1,2],[2,1],[1,0],[0,1], with an area of 2.
Example 2:
1 2 3
Input: [[0,1],[2,1],[1,1],[1,0],[2,0]] Output: 1.00000 Explanation: The minimum area rectangle occurs at [1,0],[1,1],[2,1],[2,0], with an area of 1.
Example 3:
1 2 3
Input: [[0,3],[1,2],[3,1],[1,3],[2,1]] Output: 0 Explanation: There is no possible rectangle to form from these points.
Example 4:
1 2 3
Input: [[3,1],[1,1],[0,1],[2,1],[3,3],[3,2],[0,2],[2,3]] Output: 2.00000 Explanation: The minimum area rectangle occurs at [2,1],[2,3],[3,3],[3,1], with an area of 2.
Note:
1 <= points.length <= 50
0 <= points[i][0] <= 40000
0 <= points[i][1] <= 40000
All points are distinct.
Answers within 10^-5 of the actual value will be accepted as correct.
这道题是之前那道 Minimum Area Rectangle 的拓展,虽说是拓展,但是解题思想完全不同。那道题由于矩形不能随意翻转,所以任意两个相邻的顶点一定是相同的横坐标或者纵坐标,而这道题就不一样了,矩形可以任意翻转,就不能利用之前的特点了。那该怎么办呢,这里就要利用到矩形的对角线的特点了,我们都知道矩形的两条对角线长度是相等的,而且相交于矩形的中心,这个中心可以通过两个对顶点的坐标求出来。只要找到了两组对顶点,它们的中心重合,并且表示的对角线长度相等,则一定可以组成矩形。基于这种思想,可以遍历任意两个顶点,求出它们之间的距离,和中心点的坐标,将这两个信息组成一个字符串,建立和顶点在数组中位置之间的映射,这样能组成矩形的点就被归类到一起了。接下来就是遍历这个 HashMap 了,只能取出两组顶点及更多的地方,开始遍历,分别通过顶点的坐标算出两条边的长度,然后相乘用来更新结果 res 即可,参见代码如下:
Given a wordlist, we want to implement a spellchecker that converts a query word into a correct word.
For a given query word, the spell checker handles two categories of spelling mistakes:
Capitalization: If the query matches a word in the wordlist (case-insensitive), then the query word is returned with the same case as the case in the wordlist.
Vowel Errors: If after replacing the vowels (‘a’, ‘e’, ‘i’, ‘o’, ‘u’) of the query word with any vowel individually, it matches a word in the wordlist - (case-insensitive), then the query word is returned with the same case as the match in the wordlist.
All strings in wordlist and queries consist only of english letters.
这道题给了一组单词,让实现一个拼写检查器,把查询单词转换成一个正确的单词。这个拼写检查器主要有两种功能,一种是可以忽略大小写,另一种是忽略元音的错误,所谓元音是 a,e,i,o,u,这五个字母。另外题目中还制定了一些其他规则:假如有和查询单词一模一样的单词,考虑大小写,此时应该优先返回。第二个优先级是字母及顺序都一样,但大小写可能不同的,第三个优先级是有元音错误的单词也可以返回,最后都不满足的话返回空串。首先对于第一种情况,返回和查询单词一模一样的单词,很简单,将所有单词放入一个 HashSet 中,这样就可以快速确定一个查询单词是否在原单词数组中出现过。对于第二种情况,做法是将每个单词都转为小写,然后建立小写单词和原单词之间都映射,注意对于转为小写后相同都单词,我们只映射第一个出现该小写状态的单词,后面的不用管。对于第三种情况,对于每个单词,转为小写之后,然后把所有的元音字母用特殊字符替代,比如下划线,然后也是建立这种特殊处理后的状态和原单词之间的映射。当映射都建立好了之后,就可以遍历所有的查询单词了,首先是去 HashSet 中找,若有跟该查询单词一模一样的,直接加入结果 res 中。若没有,则先将查询单词变为小写,然后去第一个 HashMap 中查找,若存在,直接加入结果 res 中。若没有,再把所有的元音变为下划线,去第二个 HashMap 中查找,存在则直接加入结果 res 中。若没有,则将空串加入结果 res 中,参见代码如下:
Given an array of integers arr, sort the array by performing a series of pancake flips.
In one pancake flip we do the following steps:
Choose an integer k where 1 <= k <= arr.length.
Reverse the sub-array arr[1…k].
For example, if arr = [3,2,1,4] and we performed a pancake flip choosing k = 3, we reverse the sub-array [3,2,1], so arr = [1,2,3,4] after the pancake flip at k = 3.
Return the k-values corresponding to a sequence of pancake flips that sort arr. Any valid answer that sorts the array within 10 * arr.length flips will be judged as correct.
Example 1:
1 2 3 4 5 6 7 8 9
Input: arr = [3,2,4,1] Output: [4,2,4,3] Explanation: We perform 4 pancake flips, with k values 4, 2, 4, and 3. Starting state: arr = [3, 2, 4, 1] After 1st flip (k = 4): arr = [1, 4, 2, 3] After 2nd flip (k = 2): arr = [4, 1, 2, 3] After 3rd flip (k = 4): arr = [3, 2, 1, 4] After 4th flip (k = 3): arr = [1, 2, 3, 4], which is sorted.
Notice that we return an array of the chosen k values of the pancake flips.
Example 2:
1 2 3
Input: arr = [1,2,3] Output: [] Explanation: The input is already sorted, so there is no need to flip anything.
Note that other answers, such as [3, 3], would also be accepted.
Constraints:
1 <= arr.length <= 100
1 <= arr[i] <= arr.length
All integers in arr are unique (i.e. arr is a permutation of the integers from 1 to arr.length).
这道题给了长度为n的数组,由1到n的组成,顺序是打乱的。现在说我们可以任意翻转前k个数字,k的范围是1到n,问怎么个翻转法能将数组翻成有序的。题目说并不限定具体的翻法,只要在 10*n 的次数内翻成有序的都是可以的,任你随意翻,就算有无效的步骤也无所谓。题目中给的例子1其实挺迷惑的,因为并不知道为啥要那样翻,也没有一个固定的翻法,所以可能会误导大家。必须要自己想出一个固定的翻法,这样才能应对所有的情况。每次先将数组中最大数字找出来,然后将最大数字翻转到首位置,然后翻转整个数组,这样最大数字就跑到最后去了。然后将最后面的最大数字去掉,这样又重现一样的情况,重复同样的步骤,直到数组只剩一个数字1为止,在过程中就把每次要翻转的位置都记录到结果 res 中就可以了,注意这里 C++ 的翻转函数 reverse 的结束位置是开区间,很容易出错,参见代码如下:
Given two positive integers x and y, an integer is powerful if it is equal to x^i + y^j for some integers i >= 0 and j >= 0. Return a list of all powerful integers that have value less than or equal to bound.
You may return the answer in any order. In your answer, each value should occur at most once.
classSolution { public: vector<int> powerfulIntegers(int x, int y, int bound){ set<int> res; long temp; int x_max = x > 1 ? 20 : 1; int y_max = y > 1 ? 20 : 1; for(int i = 0 ; i < x_max && pow(x, i) <= bound; i ++) for(int j = 0 ; j < y_max && pow(y, j) <= bound; j ++) { temp = pow(x, i) + pow(y, j); if(temp <= bound) res.insert(temp); } returnvector<int>(res.begin(), res.end()); } };
Leetcode971. Flip Binary Tree To Match Preorder Traversal
Given a binary tree with N nodes, each node has a different value from {1, …, N}.
A node in this binary tree can be flipped by swapping the left child and the right child of that node.
Consider the sequence of N values reported by a preorder traversal starting from the root. Call such a sequence of N values the voyage of the tree.
(Recall that a preorder traversal of a node means we report the current node’s value, then preorder-traverse the left child, then preorder-traverse the right child.)
Our goal is to flip the least number of nodes in the tree so that the voyage of the tree matches the voyage we are given.
If we can do so, then return a list of the values of all nodes flipped. You may return the answer in any order.
We have a list of points on the plane. Find the K closest points to the origin (0, 0).
(Here, the distance between two points on a plane is the Euclidean distance.)
You may return the answer in any order. The answer is guaranteed to be unique (except for the order that it is in.)
Example 1:
1 2 3 4 5 6 7
Input: points = [[1,3],[-2,2]], K = 1 Output: [[-2,2]] Explanation: The distance between (1, 3) and the origin is sqrt(10). The distance between (-2, 2) and the origin is sqrt(8). Since sqrt(8) < sqrt(10), (-2, 2) is closer to the origin. We only want the closest K = 1 points from the origin, so the answer is just [[-2,2]].
Example 2:
1 2 3
Input: points = [[3,3],[5,-1],[-2,4]], K = 2 Output: [[3,3],[-2,4]] (The answer [[-2,4],[3,3]] would also be accepted.)
下面这种解法是使用最大堆 Max Heap 来做的,在 C++ 中就是用优先队列来做,这里维护一个大小为k的最大堆,里面放一个 pair 对儿,由距离原点的距离,和该点在原数组中的下标组成,这样优先队列就可以按照到原点的距离排队了,距离大的就在队首。这样每当个数超过k个了之后,就将队首的元素移除即可,最后把剩下的k个点存入结果 res 中即可,参见代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
classSolution { public: vector<vector<int>> kClosest(vector<vector<int>>& points, int K) { vector<vector<int>> res; priority_queue<pair<int, int>> pq; for (int i = 0; i < points.size(); ++i) { int t = points[i][0] * points[i][0] + points[i][1] * points[i][1]; pq.push({t, i}); if (pq.size() > K) pq.pop(); } while (!pq.empty()) { auto t = pq.top(); pq.pop(); res.push_back(points[t.second]); } return res; } };
classSolution { public: intmaxTurbulenceSize(vector<int>& A){ int sizee = A.size(); int res = 0; int count = 2; for(int i = 1; i < sizee; i ++) { if(A[i] > A[i - 1]) A[i-1] = 1; elseif(A[i] < A[i + 1]) A[i-1] = -1; else A[i-1] = 0; } for(int i = 1; i < sizee - 1; i ++) { while(i < sizee - 1 && A[i] * A[i-1] < 0) { count ++; i ++; } res = max(res, count); count = 2; } return res; } };
Leetcode979. Distribute Coins in Binary Tree
Given the root of a binary tree with N nodes, each node in the tree has node.val coins, and there are N coins total.
In one move, we may choose two adjacent nodes and move one coin from one node to another. (The move may be from parent to child, or from child to parent.)
Return the number of moves required to make every node have exactly one coin.
Example 1:
Input: [3,0,0] Output: 2 Explanation: From the root of the tree, we move one coin to its left child, and one coin to its right child.
Example 2:
Input: [0,3,0] Output: 3 Explanation: From the left child of the root, we move two coins to the root [taking two moves]. Then, we move one coin from the root of the tree to the right child.
classSolution { public: int ans=0; intdistributeCoins(TreeNode* root){ dfs(root); return ans; } intdfs(TreeNode* root){ if(root==NULL) return0; int left = dfs(root->left); int right = dfs(root->right); ans += abs(left) + abs(right); return root->val -1 + left + right ; } };
Leetcode980. Unique Paths III
On a 2-dimensional grid, there are 4 types of squares:
1 represents the starting square. There is exactly one starting square. 2 represents the ending square. There is exactly one ending square. 0 represents empty squares we can walk over. -1 represents obstacles that we cannot walk over. Return the number of 4-directional walks from the starting square to the ending square, that walk over every non-obstacle square exactly once.
Example 1:
1 2 3 4 5
Input: [[1,0,0,0],[0,0,0,0],[0,0,2,-1]] Output: 2 Explanation: We have the following two paths: 1. (0,0),(0,1),(0,2),(0,3),(1,3),(1,2),(1,1),(1,0),(2,0),(2,1),(2,2) 2. (0,0),(1,0),(2,0),(2,1),(1,1),(0,1),(0,2),(0,3),(1,3),(1,2),(2,2)
Example 2:
1 2 3 4 5 6 7
Input: [[1,0,0,0],[0,0,0,0],[0,0,0,2]] Output: 4 Explanation: We have the following four paths: 1. (0,0),(0,1),(0,2),(0,3),(1,3),(1,2),(1,1),(1,0),(2,0),(2,1),(2,2),(2,3) 2. (0,0),(0,1),(1,1),(1,0),(2,0),(2,1),(2,2),(1,2),(0,2),(0,3),(1,3),(2,3) 3. (0,0),(1,0),(2,0),(2,1),(2,2),(1,2),(1,1),(0,1),(0,2),(0,3),(1,3),(2,3) 4. (0,0),(1,0),(2,0),(2,1),(1,1),(0,1),(0,2),(0,3),(1,3),(1,2),(2,2),(2,3)
Example 3:
1 2 3 4 5
Input: [[0,1],[2,0]] Output: 0 Explanation: There is no path that walks over every empty square exactly once. Note that the starting and ending square can be anywhere in the grid.
classSolution { public: vector<pair<int, int>> dirs = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; intuniquePathsIII(vector<vector<int>>& grid){ int M=grid.size(); int zerosize=0,res=0; int N=grid[0].size(); for(int i=0;i<M;i++) for(int j=0;j<N;j++) if(grid[i][j]==0) zerosize++; for(int i=0;i<M;i++) for(int j=0;j<N;j++) if(grid[i][j]==1) dfs(grid,i,j,0,zerosize,res); return res; } voiddfs(vector<vector<int>>& grid, int x, int y, int pathcount, int zerocount, int& res){ if(grid[x][y]==2 && zerocount == pathcount ){ res++; } int M=grid.size(); int N=grid[0].size(); int pre=grid[x][y]; if(pre==0) pathcount++; grid[x][y]=-1; for (auto d : dirs) { int nx = x + d.first; int ny = y + d.second; if (nx < 0 || nx >= M || ny < 0 || ny >= N || grid[nx][ny] == -1) continue; dfs(grid, nx, ny, pathcount, zerocount, res); } grid[x][y]=pre;
} };
Leetcode981. Time Based Key-Value Store
Create a timebased key-value store class TimeMap, that supports two operations.
set(string key, string value, int timestamp):Stores the key and value, along with the given timestamp.
get(string key, int timestamp)
Returns a value such that set(key, value, timestamp_prev) was called previously, with timestamp_prev <= timestamp.
If there are multiple such values, it returns the one with the largest timestamp_prev.
If there are no values, it returns the empty string (“”).
Example 1:
1 2 3 4 5 6 7 8 9 10
Input: inputs = ["TimeMap","set","get","get","set","get","get"], inputs = [[],["foo","bar",1],["foo",1],["foo",3],["foo","bar2",4],["foo",4],["foo",5]] Output: [null,null,"bar","bar",null,"bar2","bar2"] Explanation: TimeMap kv; kv.set("foo", "bar", 1); // store the key "foo" and value "bar" along with timestamp = 1 kv.get("foo", 1); // output "bar" kv.get("foo", 3); // output "bar" since there is no value corresponding to foo at timestamp 3 and timestamp 2, then the only value is at timestamp 1 ie "bar" kv.set("foo", "bar2", 4); kv.get("foo", 4); // output "bar2" kv.get("foo", 5); //output "bar2"
classTimeMap { private: unordered_map<string, map<int, string>> mp; vector<int> tvec; public: /** Initialize your data structure here. */ TimeMap() {}
voidset(string key, string value, int timestamp){ mp[key][timestamp] = value; }
string get(string key, int timestamp){ if(!mp.count(key)) return""; if(mp[key].count(timestamp)) return mp[key][timestamp]; for(auto it = mp[key].rbegin(); it != mp[key].rend(); it++) { if(it->first > timestamp) continue; else return it->second; } return""; } };
Leetcode983. Minimum Cost For Tickets
In a country popular for train travel, you have planned some train travelling one year in advance. The days of the year that you will travel is given as an array days. Each day is an integer from 1 to 365.
Train tickets are sold in 3 different ways:
a 1-day pass is sold for costs[0] dollars;
a 7-day pass is sold for costs[1] dollars;
a 30-day pass is sold for costs[2] dollars.
The passes allow that many days of consecutive travel. For example, if we get a 7-day pass on day 2, then we can travel for 7 days: day 2, 3, 4, 5, 6, 7, and 8.
Return the minimum number of dollars you need to travel every day in the given list of days.
Example 1:
1 2 3 4 5 6 7 8
Input: days = [1,4,6,7,8,20], costs = [2,7,15] Output: 11 Explanation: For example, here is one way to buy passes that lets you travel your travel plan: On day 1, you bought a 1-day pass for costs[0] = $2, which covered day 1. On day 3, you bought a 7-day pass for costs[1] = $7, which covered days 3, 4, ..., 9. On day 20, you bought a 1-day pass for costs[0] = $2, which covered day 20. In total you spent $11 and covered all the days of your travel.
Example 2:
1 2 3 4 5 6 7
Input: days = [1,2,3,4,5,6,7,8,9,10,30,31], costs = [2,7,15] Output: 17 Explanation: For example, here is one way to buy passes that lets you travel your travel plan: On day 1, you bought a 30-day pass for costs[2] = $15 which covered days 1, 2, ..., 30. On day 31, you bought a 1-day pass for costs[0] = $2 which covered day 31. In total you spent $17 and covered all the days of your travel.
classSolution { public: string strWithout3a3b(int A, int B){ char a = 'a', b = 'b'; int temp2; string res = ""; if(A < B) { a = 'b'; b = 'a'; temp2 = A; A = B; B = temp2; } while(A>0 || B>0) { if(A > 0) { res += a; A --; } if(A > B) { res += a; A --; } if(B > 0) { res += b; B --; } } return res; } };
Leetcode985. Sum of Even Numbers After Queries
We have an array A of integers, and an array queries of queries.
For the i-th query val = queries[i][0], index = queries[i][1], we add val to A[index]. Then, the answer to the i-th query is the sum of the even values of A.
(Here, the given index = queries[i][1] is a 0-based index, and each query permanently modifies the array A.)
Return the answer to all queries. Your answer array should have answer[i] as the answer to the i-th query.
Example 1:
1 2 3 4 5 6 7 8
Input: A = [1,2,3,4], queries = [[1,0],[-3,1],[-4,0],[2,3]] Output: [8,6,2,4] Explanation: At the beginning, the array is [1,2,3,4]. After adding 1 to A[0], the array is [2,2,3,4], and the sum of even values is 2 + 2 + 4 = 8. After adding -3 to A[1], the array is [2,-1,3,4], and the sum of even values is 2 + 4 = 6. After adding -4 to A[0], the array is [-2,-1,3,4], and the sum of even values is -2 + 4 = 2. After adding 2 to A[3], the array is [-2,-1,3,6], and the sum of even values is -2 + 6 = 4.
classSolution { public: vector<int> sumEvenAfterQueries(vector<int>& A, vector<vector<int>>& queries){ vector<int> res; int size = queries.size(), val, index, sum = 0; for(int i = 0; i < A.size(); i ++) sum += (A[i]%2 ? 0 : A[i]); for(int i = 0; i < size; i ++) { val = queries[i][0]; index = queries[i][1]; if(A[index]%2 == 0) sum -= A[index]; A[index] += val; if(A[index]%2 == 0) sum += A[index]; res.push_back(sum); } return res; } };
Leetcode986. Interval List Intersections
Given two lists of closed intervals, each list of intervals is pairwise disjoint and in sorted order.
Return the intersection of these two interval lists.
(Formally, a closed interval [a, b] (with a <= b) denotes the set of real numbers x with a <= x <= b. The intersection of two closed intervals is a set of real numbers that is either empty, or can be represented as a closed interval. For example, the intersection of [1, 3] and [2, 4] is [2, 3].)
Leetcode987. Vertical Order Traversal of a Binary Tree
Given a binary tree, return the vertical order traversal of its nodes values.
For each node at position (X, Y), its left and right children respectively will be at positions (X-1, Y-1) and (X+1, Y-1).
Running a vertical line from X = -infinity to X = +infinity, whenever the vertical line touches some nodes, we report the values of the nodes in order from top to bottom (decreasing Y coordinates).
If two nodes have the same position, then the value of the node that is reported first is the value that is smaller.
Return an list of non-empty reports in order of X coordinate. Every report will have a list of values of nodes.
Example 1:
1 2 3 4 5 6 7 8
Input: [3,9,20,null,null,15,7] Output: [[9],[3,15],[20],[7]] Explanation: Without loss of generality, we can assume the root node is at position (0, 0): Then, the node with value 9 occurs at position (-1, -1); The nodes with values 3 and 15 occur at positions (0, 0) and (0, -2); The node with value 20 occurs at position (1, -1); The node with value 7 occurs at position (2, -2).
Example 2:
1 2 3 4 5
Input: [1,2,3,4,5,6,7] Output: [[4],[2],[1,5,6],[3],[7]] Explanation: The node with value 5 and the node with value 6 have the same position according to the given scheme. However, in the report "[1,5,6]", the node value of 5 comes first since 5 is smaller than 6.
Given the root of a binary tree, each node has a value from 0 to 25 representing the letters ‘a’ to ‘z’: a value of 0 represents ‘a’, a value of 1 represents ‘b’, and so on.
Find the lexicographically smallest string that starts at a leaf of this tree and ends at the root.
(As a reminder, any shorter prefix of a string is lexicographically smaller: for example, “ab” is lexicographically smaller than “aba”. A leaf of a node is a node that has no children.)
classSolution { public: string result = "zzzzzzzzzz"; voidbianli(TreeNode* root, string cur){ if(root->left == NULL && root->right == NULL) { cur = (char)((root->val) + 'a')+cur; result = result < cur ? result : cur; return; } if(root->left != NULL) { bianli(root->left, (char)(root->val+'a')+cur); } if(root->right != NULL) { bianli(root->right, (char)(root->val+'a')+cur); } } string smallestFromLeaf(TreeNode* root){ bianli(root, ""); return result; } };
Leetcode989. Add to Array-Form of Integer
For a non-negative integer X, the array-form of X is an array of its digits in left to right order. For example, if X = 1231, then the array form is [1,2,3,1].
Given the array-form A of a non-negative integer X, return the array-form of the integer X+K.
Example 1:
1 2 3
Input: A = [1,2,0,0], K = 34 Output: [1,2,3,4] Explanation: 1200 + 34 = 1234
Example 2:
1 2 3
Input: A = [2,7,4], K = 181 Output: [4,5,5] Explanation: 274 + 181 = 455
Example 3:
1 2 3
Input: A = [2,1,5], K = 806 Output: [1,0,2,1] Explanation: 215 + 806 = 1021
Example 4:
1 2 3
Input: A = [9,9,9,9,9,9,9,9,9,9], K = 1 Output: [1,0,0,0,0,0,0,0,0,0,0] Explanation: 9999999999 + 1 = 10000000000
classSolution { public List<Integer> addToArrayForm(int[] A, int K) { intlen= A.length; intcarry=0; List<Integer> list = newArrayList<Integer>(); for(inti= len - 1; i > -1; i--){ intsum= carry + A[i] + K % 10; list.add(sum % 10); carry = sum / 10; K /= 10; } carry += K; while(carry != 0){ list.add(carry % 10); carry /= 10; } Collections.reverse(list); return list; } }
Leetcode990. Satisfiability of Equality Equations
Given an array equations of strings that represent relationships between variables, each string equations[i] has length 4 and takes one of two different forms: “a==b” or “a!=b”. Here, a and b are lowercase letters (not necessarily different) that represent one-letter variable names.
Return true if and only if it is possible to assign integers to variable names so as to satisfy all the given equations.
Example 1:
1 2 3
Input: ["a==b","b!=a"] Output: false Explanation: If we assign say, a = 1 and b = 1, then the first equation is satisfied, but not the second. There is no way to assign the variables to satisfy both equations.
Example 2:
1 2 3
Input: ["b==a","a==b"] Output: true Explanation: We could assign a = 1 and b = 1 to satisfy both equations.
Example 3:
1 2
Input: ["a==b","b==c","a==c"] Output: true
Example 4:
1 2
Input: ["a==b","b!=c","c==a"] Output: false
Example 5:
1 2
Input: ["c==c","b==d","x!=z"] Output: true
Note:
1 <= equations.length <= 500
equations[i].length == 4
equations[i][0] and equations[i][3] are lowercase letters
equations[i][1] is either ‘=’ or ‘!’
equations[i][2] is ‘=’
给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 equations[i] 的长度为 4,并采用两种不同的形式之一:”a==b”或 “a!=b”。在这里,a 和 b 是小写字母(不一定不同),表示单字母变量名。
classSolution { public: intbrokenCalc(int X, int Y){ if (X >= Y) return X - Y; return (Y % 2 == 0) ? (1 + brokenCalc(X, Y / 2)) : (1 + brokenCalc(X, Y + 1)); } };
Leetcode993. Cousins in Binary Tree
In a binary tree, the root node is at depth 0, and children of each depth k node are at depth k+1.
Two nodes of a binary tree are cousins if they have the same depth, but have different parents. We are given the root of a binary tree with unique values, and the values x and y of two different nodes in the tree. Return true if and only if the nodes corresponding to the values x and y are cousins.
Example 1:
1 2
Input: root = [1,2,3,4], x = 4, y = 3 Output: false
Example 2:
1 2
Input: root = [1,2,3,null,4,null,5], x = 5, y = 4 Output: true
Example 3:
1 2
Input: root = [1,2,3,null,4], x = 2, y = 3 Output: false
TreeNode *right = dfs(root->right, x, depth + 1, level); if (right) return right; returnNULL; } boolisCousins(TreeNode* root, int x, int y){ int level_a, level_b; TreeNode *xx = dfs(root, x, 0, level_a); TreeNode *yy = dfs(root, y, 0, level_b); if(xx != yy && level_a == level_b) returntrue; returnfalse; } };
Leetcode994. Rotting Oranges
You are given an m x n grid where each cell can have one of three values:
0 representing an empty cell,
1 representing a fresh orange, or
2 representing a rotten orange.
Every minute, any fresh orange that is 4-directionally adjacent to a rotten orange becomes rotten.
Return the minimum number of minutes that must elapse until no cell has a fresh orange. If this is impossible, return -1.
Example 1:
1 2
Input: grid = [[2,1,1],[1,1,0],[0,1,1]] Output: 4
Example 2:
1 2 3
Input: grid = [[2,1,1],[0,1,1],[1,0,1]] Output: -1 Explanation: The orange in the bottom left corner (row 2, column 0) is never rotten, because rotting only happens 4-directionally.
Example 3:
1 2 3
Input: grid = [[0,2]] Output: 0 Explanation: Since there are already no fresh oranges at minute 0, the answer is just 0.
Constraints:
m == grid.length
n == grid[i].length
1 <= m, n <= 10
grid[i][j] is 0, 1, or 2.
这道题说给的一个 mxn 大小的格子上有些新鲜和腐烂的橘子,每一分钟腐烂的橘子都会传染给其周围四个中的新鲜橘子,使得其也变得腐烂。现在问需要多少分钟可以使得所有的新鲜橘子都变腐烂,无法做到时返回 -1。由于这里新鲜的橘子自己不会变腐烂,只有被周围的腐烂橘子传染才会,所以当新鲜橘子周围不会出现腐烂橘子的时候,那么这个新鲜橘子就不会腐烂,这才会有返回 -1 的情况。这道题就是个典型的广度优先遍历 Breadth First Search,并没有什么太大的难度,先遍历一遍整个二维数组,统计出所有新鲜橘子的个数,并把腐烂的橘子坐标放入一个队列 queue,之后进行 while 循环,循环条件是队列不会空,且 freshLeft 大于0,使用层序遍历的方法,用个 for 循环在内部。每次取出队首元素,遍历其周围四个位置,越界或者不是新鲜橘子都跳过,否则将新鲜橘子标记为腐烂,加入队列中,并且 freshLeft 自减1。每层遍历完成之后,结果 res 自增1,最后返回的时候,若还有新鲜橘子,即 freshLeft 大于0时,返回 -1,否则返回 res 即可,参见代码如下:
classSolution { public: intorangesRotting(vector<vector<int>>& grid){ int res = 0, m = grid.size(), n = grid[0].size(), freshLeft = 0; queue<pair<int, int> > q; vector<vector<int>> dirs{{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; for (int i = 0; i < m; i ++) for (int j = 0; j < n; j ++) { if (grid[i][j] == 1) freshLeft ++; elseif (grid[i][j] == 2) q.push(make_pair(i, j)); } while(!q.empty() && freshLeft > 0) { int size = q.size(); for (int i = 0; i < size; i ++) { int x = q.front().first, y = q.front().second; q.pop(); for (int j = 0; j < 4; j ++) { int xx = x + dirs[j][0]; int yy = y + dirs[j][1]; if (0 > xx || xx >= m || 0 > yy || yy >= n || grid[xx][yy] != 1) continue; grid[xx][yy] = 2; q.push(make_pair(xx, yy)); freshLeft --; } } res ++; } return freshLeft > 0 ? -1 : res; } };
Leetcode997. Find the Town Judge
In a town, there are N people labelled from 1 to N. There is a rumor that one of these people is secretly the town judge.
If the town judge exists, then:
The town judge trusts nobody. Everybody (except for the town judge) trusts the town judge. There is exactly one person that satisfies properties 1 and 2. You are given trust, an array of pairs trust[i] = [a, b] representing that the person labelled a trusts the person labelled b.
If the town judge exists and can be identified, return the label of the town judge. Otherwise, return -1.
Example 1:
1 2
Input: N = 2, trust = [[1,2]] Output: 2
Example 2:
1 2
Input: N = 3, trust = [[1,3],[2,3]] Output: 3
Example 3:
1 2
Input: N = 3, trust = [[1,3],[2,3],[3,1]] Output: -1
Example 4:
1 2
Input: N = 3, trust = [[1,2],[2,3]] Output: -1
Example 5:
1 2
Input: N = 4, trust = [[1,3],[1,4],[2,3],[2,4],[4,3]] Output: 3
在一个小镇里,按从 1 到 N 标记了 N 个人。传言称,这些人中有一个是小镇上的秘密法官。如果小镇的法官真的存在,那么:小镇的法官不相信任何人。每个人(除了小镇法官外)都信任小镇的法官。只有一个人同时满足属性 1 和属性 2 。给定数组 trust,该数组由信任对 trust[i] = [a, b] 组成,表示标记为 a 的人信任标记为 b 的人。如果小镇存在秘密法官并且可以确定他的身份,请返回该法官的标记。否则,返回 -1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
classSolution { public: intfindJudge(int N, vector<vector<int>>& trust){ int in[1005] = {0}, out[1005] = {0}; for(int i = 0; i < trust.size(); i ++) { in[trust[i][1]] ++; out[trust[i][0]] = -1; } for(int i = 1; i <= N; i ++) { if(out[i] == 0 && in[i] == N - 1) return i; } return-1; } };
Leetcode998. Maximum Binary Tree II
We are given the root node of a maximum tree: a tree where every node has a value greater than any other value in its subtree. Just as in the previous problem, the given tree was constructed from an list A (root = Construct(A)) recursively with the following Construct(A) routine:
If A is empty, return null.
Otherwise, let A[i] be the largest element of A. Create a root node with value A[i].
The left child of root will be Construct([A[0], A[1], …, A[i-1]])
The right child of root will be Construct([A[i+1], A[i+2], …, A[A.length - 1]])
Return root.
Note that we were not given A directly, only a root node root = Construct(A).
Suppose B is a copy of A with the value val appended to it. It is guaranteed that B has unique values.
Return Construct(B).
Example 1:
1 2 3
Input: root = [4,1,3,null,null,2], val = 5 Output: [5,4,null,1,3,null,null,2] Explanation: A = [1,4,2,3], B = [1,4,2,3,5]
Example 2:
1 2 3
Input: root = [5,2,4,null,1], val = 3 Output: [5,2,4,null,1,null,3] Explanation: A = [2,1,5,4], B = [2,1,5,4,3]
Example 3:
1 2 3
Input: root = [5,2,3,null,1], val = 4 Output: [5,2,4,null,1,3] Explanation: A = [2,1,5,3], B = [2,1,5,3,4]
classSolution { public: TreeNode* insertIntoMaxTree(TreeNode* root, int val){ TreeNode* node = newTreeNode(val); if (!root || root -> val < val) { node -> left = root; return node; } root -> right = insertIntoMaxTree(root -> right, val); return root; } };
Leetcode999. Available Captures for Rook
On an 8 x 8 chessboard, there is one white rook. There also may be empty squares, white bishops, and black pawns. These are given as characters ‘R’, ‘.’, ‘B’, and ‘p’ respectively. Uppercase characters represent white pieces, and lowercase characters represent black pieces.
The rook moves as in the rules of Chess: it chooses one of four cardinal directions (north, east, west, and south), then moves in that direction until it chooses to stop, reaches the edge of the board, or captures an opposite colored pawn by moving to the same square it occupies. Also, rooks cannot move into the same square as other friendly bishops.
Return the number of pawns the rook can capture in one move.
Example 1:
1 2 3 4 5 6 7 8 9 10 11 12
Input: [[".",".",".",".",".",".",".","."], [".",".",".","p",".",".",".","."], [".",".",".","R",".",".",".","p"], [".",".",".",".",".",".",".","."], [".",".",".",".",".",".",".","."], [".",".",".","p",".",".",".","."], [".",".",".",".",".",".",".","."], [".",".",".",".",".",".",".","."]] Output: 3 Explanation: In this example the rook is able to capture all the pawns.
Example 2:
1 2 3 4 5 6 7 8 9 10 11 12
Input: [ [".",".",".",".",".",".",".","."], [".","p","p","p","p","p",".","."], [".","p","p","B","p","p",".","."], [".","p","B","R","B","p",".","."], [".","p","p","B","p","p",".","."], [".","p","p","p","p","p",".","."], [".",".",".",".",".",".",".","."], [".",".",".",".",".",".",".","."]] Output: 0 Explanation: Bishops are blocking the rook to capture any pawn.
Example 3:
1 2 3 4 5 6 7 8 9 10 11 12
Input: [ [".",".",".",".",".",".",".","."], [".",".",".","p",".",".",".","."], [".",".",".","p",".",".",".","."], ["p","p",".","R",".","p","B","."], [".",".",".",".",".",".",".","."], [".",".",".","B",".",".",".","."], [".",".",".","p",".",".",".","."], [".",".",".",".",".",".",".","."]] Output: 3 Explanation: The rook can capture the pawns at positions b5, d6 and f5.
sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/mthread$ ./mthread Welcome to per thread arena example::6501 Before malloc in main thread After malloc and before free in main thread After free in main thread ... sploitfun@sploitfun-VirtualBox:~/lsploits/hof/ptmalloc.ppt/mthread$ cat /proc/6501/maps 08048000-08049000 r-xp 00000000 08:01 539625 /home/sploitfun/ptmalloc.ppt/mthread/mthread 08049000-0804a000 r--p 00000000 08:01 539625 /home/sploitfun/ptmalloc.ppt/mthread/mthread 0804a000-0804b000 rw-p 00001000 08:01 539625 /home/sploitfun/ptmalloc.ppt/mthread/mthread 0804b000-0806c000 rw-p 00000000 00:00 0 [heap] b7e05000-b7e07000 rw-p 00000000 00:00 0 ... sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/mthread$
在 thread1 malloc 之前
从如下的输出结果中我们可以看到,此时 thread1 的堆尚不存在,但其栈已产生。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/mthread$ ./mthread Welcome to per thread arena example::6501 Before malloc in main thread After malloc and before free in main thread After free in main thread Before malloc in thread 1 ... sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/mthread$ cat /proc/6501/maps 08048000-08049000 r-xp 00000000 08:01 539625 /home/sploitfun/ptmalloc.ppt/mthread/mthread 08049000-0804a000 r--p 00000000 08:01 539625 /home/sploitfun/ptmalloc.ppt/mthread/mthread 0804a000-0804b000 rw-p 00001000 08:01 539625 /home/sploitfun/ptmalloc.ppt/mthread/mthread 0804b000-0806c000 rw-p 00000000 00:00 0 [heap] b7604000-b7605000 ---p 00000000 00:00 0 b7605000-b7e07000 rw-p 00000000 00:00 0 [stack:6594] ... sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/mthread$
ploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/mthread$ ./mthread Welcome to per thread arena example::6501 Before malloc in main thread After malloc and before free in main thread After free in main thread Before malloc in thread 1 After malloc and before free in thread 1 ... sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/mthread$ cat /proc/6501/maps 08048000-08049000 r-xp 00000000 08:01 539625 /home/sploitfun/ptmalloc.ppt/mthread/mthread 08049000-0804a000 r--p 00000000 08:01 539625 /home/sploitfun/ptmalloc.ppt/mthread/mthread 0804a000-0804b000 rw-p 00001000 08:01 539625 /home/sploitfun/ptmalloc.ppt/mthread/mthread 0804b000-0806c000 rw-p 00000000 00:00 0 [heap] b7500000-b7521000 rw-p 00000000 00:00 0 b7521000-b7600000 ---p 00000000 00:00 0 b7604000-b7605000 ---p 00000000 00:00 0 b7605000-b7e07000 rw-p 00000000 00:00 0 [stack:6594] ... sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/mthread$
sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/mthread$ ./mthread Welcome to per thread arena example::6501 Before malloc in main thread After malloc and before free in main thread After free in main thread Before malloc in thread 1 After malloc and before free in thread 1 After free in thread 1 ... sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/mthread$ cat /proc/6501/maps 08048000-08049000 r-xp 00000000 08:01 539625 /home/sploitfun/ptmalloc.ppt/mthread/mthread 08049000-0804a000 r--p 00000000 08:01 539625 /home/sploitfun/ptmalloc.ppt/mthread/mthread 0804a000-0804b000 rw-p 00001000 08:01 539625 /home/sploitfun/ptmalloc.ppt/mthread/mthread 0804b000-0806c000 rw-p 00000000 00:00 0 [heap] b7500000-b7521000 rw-p 00000000 00:00 0 b7521000-b7600000 ---p 00000000 00:00 0 b7604000-b7605000 ---p 00000000 00:00 0 b7605000-b7e07000 rw-p 00000000 00:00 0 [stack:6594] ... sploitfun@sploitfun-VirtualBox:~/ptmalloc.ppt/mthread$
不像 small bin ,large bin 中所有 chunk 大小不一定相同,各 chunk 大小递减保存。最大的 chunk 保存顶端,而最小的 chunk 保存在尾端;
合并 —— 两个相邻的空闲 chunk 会被合并;
malloc(large chunk)
初始情况下,large bin 都会是 NULL,因此尽管用户请求 large chunk ,提供服务的将是 next largetst bin 路径而不是 large bin 路劲 。
第一次调用 malloc 时,维护在 malloc_state 中的 small bin 和 large bin 将被初始化,它们都会指向自身以表示其为空;
此后当 large bin 非空,如果相应 bin 中的最大 chunk 大小大于用户请求大小,分配器就从该 bin 顶端遍历到尾端,以找到一个大小最接近用户请求的 chunk。一旦找到,相应 chunk 就会被切分成两块:
User chunk(用户请求大小)—— 返回给用户;
Remainder chunk (剩余大小)—— 添加到 unsorted bin。
如果相应 bin 中的最大 chunk 大小小于用户请求大小,分配器就会扫描 binmaps,从而查找最小非空 bin。如果找到了这样的 - bin,就从中选择合适的 chunk 并切割给用户;反之就使用 top chunk 响应用户请求。
free(large chunk) —— 类似于 small chunk 。
Top Chunk
一个 arena 中最顶部的 chunk 被称为「top chunk」。它不属于任何 bin 。当所有 bin 中都没有合适空闲内存时,就会使用 top chunk 来响应用户请求。
当 top chunk 的大小比用户请求的大小大的时候,top chunk 会分割为两个部分:
User chunk,返回给用户;
Remainder chunk,剩余部分,将成为新的 top chunk。 当 top chunk 的大小比用户请求的大小小的时候,top chunk 就通过 sbrk(main arena)或 mmap( thread arena)系统调用扩容。
Last Remainder Chunk
「last remainder chunk」即最后一次 small request 中因分割而得到的剩余部分,它有利于改进引用局部性,也即后续对 small chunk 的 malloc 请求可能最终被分配得彼此靠近。
那么 arena 中的若干 chunks,哪个有资格成为 last remainder chunk 呢?
当用户请求 small chunk 而无法从 small bin 和 unsorted bin 得到服务时,分配器就会通过扫描 binmaps 找到最小非空 bin。正如前文所提及的,如果这样的 bin 找到了,其中最合适的 chunk 就会分割为两部分:返回给用户的 User chunk 、添加到 unsorted bin 中的 Remainder chunk。这一 Remainder chunk 就将成为 last remainder chunk。
那么引用局部性是如何达成的呢?
当用户的后续请求 small chunk,并且 last remainder chunk 是 unsorted bin 中唯一的 chunk,该 last remainder chunk 就将分割成两部分:返回给用户的 User chunk、添加到 unsorted bin 中的 Remainder chunk(也是 last remainder chunk)。因此后续的请求的 chunk 最终将被分配得彼此靠近。
malloc returns a void pointer to the allocated space, or NULL if there is insufficient memory available. To return a pointer to a type other than void, use a type cast on the return value. The storage space pointed to by the return value is guaranteed to be suitably aligned for storage of any type of object. If size is 0, malloc allocates a zero-length item in the heap and returns a valid pointer to that item. Always check the return from malloc, even if the amount of memory requested is small.
First fit:从头开始,使用第一个数据区大小大于要求size的块所谓此次分配的块 Best fit:从头开始,遍历所有块,使用数据区大小大于size且差值最小的块作为此次分配的块 两种方法各有千秋,best fit具有较高的内存使用率(payload较高),而first fit具有更好的运行效率。这里我们采用first fit算法。
1 2 3 4 5 6 7 8 9
/* First fit */ t_block find_block(t_block *last, size_t size) { t_block b = first_block; while(b && !(b->free && b->size >= size)) { *last = b; b = b->next; } return b; }
有一种静态重定位的技术可以解决这个问题,它的工作原理非常简单粗暴:当 B 程序被加载到地址 16384 处之后,把 B 的所有相对内存地址都加上 16384,这样的话当 B 执行 jmp 1028 之时,其实执行的是jmp 1028+16384,就可以跳转到正确的内存地址处去执行正确的指令了,但是这种技术并不通用,而且还会对程序装载进内存的性能有影响。
再往后,就发展出来了存储器抽象:地址空间,就好像进程是 CPU 的抽象,地址空间则是存储器的抽象,每个进程都会分配独享的地址空间,但是独享的地址空间又带来了新的问题:如何实现不同进程的相同相对地址指向不同的物理地址?最开始是使用动态重定位技术来实现,这是用一种相对简单的地址空间到物理内存的映射方法。基本原理就是为每一个 CPU 配备两个特殊的硬件寄存器:基址寄存器和界限寄存器,用来动态保存每一个程序的起始物理内存地址和长度,比如前文中的 A,B 两个程序,当 A 运行时基址寄存器和界限寄存器就会分别存入 0 和 16384,而当 B 运行时则两个寄存器又会分别存入 16384 和 32768。然后每次访问指定的内存地址时,CPU 会在把地址发往内存总线之前自动把基址寄存器里的值加到该内存地址上,得到一个真正的物理内存地址,同时还会根据界限寄存器里的值检查该地址是否溢出,若是,则产生错误中止程序,动态重定位技术解决了静态重定位技术造成的程序装载速度慢的问题,但是也有新问题:每次访问内存都需要进行加法和比较运算,比较运算本身可以很快,但是加法运算由于进位传递时间的问题,除非使用特殊的电路,否则会比较慢。
先把进程 A 换入内存,然后启动进程 B 和 C,也换入内存,接着 A 被从内存交换到磁盘,然后又有新的进程 D 调入内存,用了 A 退出之后空出来的内存空间,最后 A 又被重新换入内存,由于内存布局已经发生了变化,所以 A 在换入内存之时会通过软件或者在运行期间通过硬件(基址寄存器和界限寄存器)对其内存地址进行重定位,多数情况下都是通过硬件。
中断:当外围设备完成用户请求的操作后,会向 CPU 发出相应的中断信号,这时 CPU 会暂停执行下一条即将要执行的指令而转到与中断信号对应的处理程序去执行,如果前面执行的指令是用户态下的程序,那么转换的过程自然就会是从用户态到内核态的切换。中断包括 I/O 中断、外部信号中断、各种定时器引起的时钟中断等。中断和异常类似,都是通过中断向量表来找到相应的处理程序进行处理。区别在于,中断来自处理器外部,不是由任何一条专门的指令造成,而异常是执行当前指令的结果。
通过上面的分析,我们可以得出 Linux 的内部层级可分为三大部分:
用户空间;
内核空间;
硬件。
Linux I/O
I/O 缓冲区
在 Linux 中,当程序调用各类文件操作函数后,用户数据(User Data)到达磁盘(Disk)的流程如上图所示。
图中描述了 Linux 中文件操作函数的层级关系和内存缓存层的存在位置,中间的黑色实线是用户态和内核态的分界线。
read(2)/write(2)是 Linux 系统中最基本的 I/O 读写系统调用,我们开发操作 I/O 的程序时必定会接触到它们,而在这两个系统调用和真实的磁盘读写之间存在一层称为 Kernel buffer cache 的缓冲区缓存。在 Linux 中 I/O 缓存其实可以细分为两个:Page Cache 和 Buffer Cache,这两个其实是一体两面,共同组成了 Linux 的内核缓冲区(Kernel Buffer Cache):
CPU 收到中断信号之后停止当前的工作,把当前的 PC/PSW 等寄存器压入堆栈保存现场,然后从地址总线取出设备编号,通过编号找到中断向量所包含的中断服务的入口地址,压入 PC 寄存器,开始运行磁盘中断服务,把数据从磁盘控制器的缓冲区拷贝到主存里的内核缓冲区;
最后 CPU 再把数据从内核缓冲区拷贝到用户缓冲区,完成读取操作,read()返回,切换回用户态。
DMA I/O
并发系统的性能高低究其根本,是取决于如何对 CPU 资源的高效调度和使用,而回头看前面的中断驱动 I/O 模式的流程,可以发现第 6、7 步的数据拷贝工作都是由 CPU 亲自完成的,也就是在这两次数据拷贝阶段中 CPU 是完全被占用而不能处理其他工作的,那么这里明显是有优化空间的;第 7 步的数据拷贝是从内核缓冲区到用户缓冲区,都是在主存里,所以这一步只能由 CPU 亲自完成,但是第 6 步的数据拷贝,是从磁盘控制器的缓冲区到主存,是两个设备之间的数据传输,这一步并非一定要 CPU 来完成,可以借助 DMA 来完成,减轻 CPU 的负担。
DMA 全称是 Direct Memory Access,也即直接存储器存取,是一种用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。整个过程无须 CPU 参与,数据直接通过 DMA 控制器进行快速地移动拷贝,节省 CPU 的资源去做其他工作。
通过引入 DMA,我们已经把 Linux 的 I/O 过程中的 CPU 拷贝次数从 4 次减少到了 2 次,但是 CPU 拷贝依然是代价很大的操作,对系统性能的影响还是很大,特别是那些频繁 I/O 的场景,更是会因为 CPU 拷贝而损失掉很多性能,我们需要进一步优化,降低、甚至是完全避免 CPU 拷贝。
零拷贝 (Zero-copy)
Zero-copy 是什么?
Wikipedia 的解释如下:
“Zero-copy” describes computer operations in which the CPU does not perform the task of copying data from one memory area to another. This is frequently used to save CPU cycles and memory bandwidth when transmitting a file over a network.
零拷贝技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域。这种技术通常用于通过网络传输文件时节省 CPU 周期和内存带宽。
利用 DMA 而非 CPU 来完成硬件接口和内核缓冲区之间的数据拷贝,从而解放 CPU,使之能去执行其他的任务,提升系统性能。
Zero-copy 的实现方式有哪些?
从 zero-copy 这个概念被提出以来,相关的实现技术便犹如雨后春笋,层出不穷。但是截至目前为止,并没有任何一种 zero-copy 技术能满足所有的场景需求,还是计算机领域那句无比经典的名言:”There is no silver bullet”!
而在 Linux 平台上,同样也有很多的 zero-copy 技术,新旧各不同,可能存在于不同的内核版本里,很多技术可能有了很大的改进或者被更新的实现方式所替代,这些不同的实现技术按照其核心思想可以归纳成大致的以下三类:
减少甚至避免用户空间和内核空间之间的数据拷贝:在一些场景下,用户进程在数据传输过程中并不需要对数据进行访问和处理,那么数据在 Linux 的 Page Cache 和用户进程的缓冲区之间的传输就完全可以避免,让数据拷贝完全在内核里进行,甚至可以通过更巧妙的方式避免在内核里的数据拷贝。这一类实现一般是通过增加新的系统调用来完成的,比如 Linux 中的mmap(),sendfile()以及splice()等。
内核缓冲区和用户缓冲区之间的传输优化:这种方式侧重于在用户进程的缓冲区和操作系统的页缓存之间的 CPU 拷贝的优化。这种方法延续了以往那种传统的通信方式,但更灵活。
减少甚至避免用户空间和内核空间之间的数据拷贝
mmap()
1 2 3 4
#include<sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); intmunmap(void *addr, size_t length);
一种简单的实现方案是在一次读写过程中用 Linux 的另一个系统调用mmap()替换原先的read(),mmap()也即是内存映射(memory map):把用户进程空间的一段内存缓冲区(user buffer)映射到文件所在的内核缓冲区(kernel buffer)上。
利用mmap()替换read(),配合write() 调用的整个流程如下:
用户进程调用mmap(),从用户态陷入内核态,将内核缓冲区映射到用户缓存区;
DMA 控制器将数据从硬盘拷贝到内核缓冲区;
mmap()返回,上下文从内核态切换回用户态;
用户进程调用write(),尝试把文件数据写到内核里的套接字缓冲区,再次陷入内核态;
CPU 将内核缓冲区中的数据拷贝到的套接字缓冲区;
DMA 控制器将数据从套接字缓冲区拷贝到网卡完成数据传输;
write()返回,上下文从内核态切换回用户态。
通过这种方式,有两个优点:一是节省内存空间,因为用户进程上的这一段内存是虚拟的,并不真正占据物理内存,只是映射到文件所在的内核缓冲区上,因此可以节省一半的内存占用;二是省去了一次 CPU 拷贝,对比传统的 Linux I/O 读写,数据不需要再经过用户进程进行转发了,而是直接在内核里就完成了拷贝。所以使用mmap()之后的拷贝次数是 2 次 DMA 拷贝,1 次 CPU 拷贝,加起来一共 3 次拷贝操作,比传统的 I/O 方式节省了一次 CPU 拷贝以及一半的内存,不过因为mmap()也是一个系统调用,因此用户态和内核态的切换还是 4 次。
mmap()因为既节省 CPU 拷贝次数又节省内存,所以比较适合大文件传输的场景。虽然mmap()完全是符合 POSIX 标准的,但是它也不是完美的,因为它并不总是能达到理想的数据传输性能。首先是因为数据数据传输过程中依然需要一次 CPU 拷贝,其次是内存映射技术是一个开销很大的虚拟存储操作:这种操作需要修改页表以及用内核缓冲区里的文件数据汰换掉当前 TLB 里的缓存以维持虚拟内存映射的一致性。但是,因为内存映射通常针对的是相对较大的数据区域,所以对于相同大小的数据来说,内存映射所带来的开销远远低于 CPU 拷贝所带来的开销。此外,使用mmap()还可能会遇到一些需要值得关注的特殊情况,例如,在mmap()—>write()这两个系统调用的整个传输过程中,如果有其他的进程突然截断了这个文件,那么这时用户进程就会因为访问非法地址而被一个从总线传来的 SIGBUS 中断信号杀死并且产生一个 core dump。有两种解决办法:
sendfile()相较于mmap()的另一个优势在于数据在传输过程中始终没有越过用户态和内核态的边界,因此极大地减少了存储管理的开销。即便如此,sendfile() 依然是一个适用性很窄的技术,最适合的场景基本也就是一个静态文件服务器了。而且根据 Linus 在 2001 年和其他内核维护者的邮件列表内容,其实当初之所以决定在 Linux 上实现sendfile()仅仅是因为在其他操作系统平台上已经率先实现了,而且有大名鼎鼎的 Apache Web 服务器已经在使用了,为了兼容 Apache Web 服务器才决定在 Linux 上也实现这个技术,而且sendfile()实现上的简洁性也和 Linux 内核的其他部分集成得很好,所以 Linus 也就同意了这个提案。
然而sendfile()本身是有很大问题的,从不同的角度来看的话主要是:
首先一个是这个接口并没有进行标准化,导致sendfile()在 Linux 上的接口实现和其他类 Unix 系统的实现并不相同;
基于这种方案,我们就可以把这仅剩的唯一一次 CPU 拷贝也给去除了(严格来说还是会有一次,但是因为这次 CPU 拷贝的只是那些微乎其微的元信息,开销几乎可以忽略不计),理论上,数据传输过程就再也没有 CPU 的参与了,也因此 CPU 的高速缓存再不会被污染了,也不再需要 CPU 来计算数据校验和了,CPU 可以去执行其他的业务计算任务,同时和 DMA 的 I/O 任务并行,此举能极大地提升系统性能。
SPLICE_F_MOVE:指示splice()尝试仅仅是移动内存页面而不是复制,设置了这个值不代表就一定不会复制内存页面,复制还是移动取决于内核能否从管道中移动内存页面,或者管道中的内存页面是否是完整的;这个标记的初始实现有很多 bug,所以从 Linux 2.6.21 版本开始就已经无效了,但还是保留了下来,因为在未来的版本里可能会重新被实现。
splice()在 Linux 内核源码中的内部实现是do_splice()函数,而写入读出管道则分别是通过do_splice_to()和do_splice_from(),这里我们重点来解析下写入管道的源码,也就是do_splice_to(),我现在手头的 Linux 内核版本是 v4.8.17,我们就基于这个版本来分析,至于读出的源码函数do_splice_from(),原理是相通的,大家举一反三即可。
至于说splice()本身的 API 为什么还是这种使用模式,那是因为 Linux 内核开发团队一直想把基于管道的这个限制去掉,但不知道因为什么一直搁置,所以这个 API 也就一直没变化,只能等内核团队哪天想起来了这一茬,然后重构一下使之不再依赖管道,在那之前,使用splice()依然还是需要额外创建管道来作为中间缓冲,如果你的业务场景很适合使用splice(),但又是性能敏感的,不想频繁地创建销毁 pipe buffer 管道缓冲区,那么可以参考一下 HAProxy 使用splice()时采用的优化方案:预先分配一个 pipe buffer pool 缓存管道,每次调用spclie()的时候去缓存池里取一个管道,用完就放回去,循环利用,提升性能。
send() with MSG_ZEROCOPY
Linux 内核在 2017 年的 v4.14 版本接受了来自 Google 工程师 Willem de Bruijn 在 TCP 网络报文的通用发送接口send()中实现的 zero-copy 功能 (MSG_ZEROCOPY) 的 patch,通过这个新功能,用户进程就能够把用户缓冲区的数据通过零拷贝的方式经过内核空间发送到网络套接字中去,这个新技术和前文介绍的几种零拷贝方式相比更加先进,因为前面几种零拷贝技术都是要求用户进程不能处理加工数据而是直接转发到目标文件描述符中去的。Willem de Bruijn 在他的论文里给出的压测数据是:采用 netperf 大包发送测试,性能提升 39%,而线上环境的数据发送性能则提升了 5%~8%,官方文档陈述说这个特性通常只在发送 10KB 左右大包的场景下才会有显著的性能提升。一开始这个特性只支持 TCP,到内核 v5.0 版本之后才支持 UDP。
这个功能的使用模式如下:
1 2 3 4
if (setsockopt(socket_fd, SOL_SOCKET, SO_ZEROCOPY, &one, sizeof(one))) error(1, errno, "setsockopt zerocopy");
ret = send(socket_fd, buffer, sizeof(buffer), MSG_ZEROCOPY);
前面我们介绍过利用内存映射技术来减少数据在用户空间和内核空间之间的复制,通常简单模式下,用户进程是对共享的缓冲区进行同步阻塞读写的,这样不会有 data race 问题,但是这种模式下效率并不高,而提升效率的一种方法就是异步地对共享缓冲区进行读写,而这样的话就必须引入保护机制来避免数据冲突问题,写时复制 (Copy on Write) 就是这样的一种技术。
共享缓冲区技术的实现需要依赖于用户进程、操作系统内核、以及 I/O 子系统 (设备驱动程序,文件系统等)之间协同工作。比如,设计得不好的用户进程容易就会修改已经发送出去的fbuf从而污染数据,更要命的是这种问题很难 debug。虽然这个技术的设计方案非常精彩,但是它的门槛和限制却不比前面介绍的其他技术少:首先会对操作系统 API 造成变动,需要使用新的一些 API 调用,其次还需要设备驱动程序配合改动,还有由于是内存共享,内核需要很小心谨慎地实现对这部分共享的内存进行数据保护和同步的机制,而这种并发的同步机制是非常容易出 bug 的从而又增加了内核的代码复杂度,等等。因此这一类的技术还远远没有到发展成熟和广泛应用的阶段,目前大多数的实现都还处于实验阶段。
总结
本文中我主要讲解了 Linux I/O 底层原理,然后介绍并解析了 Linux 中的 Zero-copy 技术,并给出了 Linux 对 I/O 模块的优化和改进思路。
Linux 的 Zero-copy 技术可以归纳成以下三大类:
减少甚至避免用户空间和内核空间之间的数据拷贝:在一些场景下,用户进程在数据传输过程中并不需要对数据进行访问和处理,那么数据在 Linux 的 Page Cache 和用户进程的缓冲区之间的传输就完全可以避免,让数据拷贝完全在内核里进行,甚至可以通过更巧妙的方式避免在内核里的数据拷贝。这一类实现一般是是通过增加新的系统调用来完成的,比如 Linux 中的mmap(),sendfile() 以及splice()等。
内核缓冲区和用户缓冲区之间的传输优化:这种方式侧重于在用户进程的缓冲区和操作系统的页缓存之间的 CPU 拷贝的优化。这种方法延续了以往那种传统的通信方式,但更灵活。
本文从虚拟内存、I/O 缓冲区,用户态&内核态以及 I/O 模式等等知识点全面而又详尽地剖析了 Linux 系统的 I/O 底层原理,分析了 Linux 传统的 I/O 模式的弊端,进而引入 Linux Zero-copy 零拷贝技术的介绍和原理解析,通过将零拷贝技术和传统的 I/O 模式进行区分和对比,带领读者经历了 Linux I/O 的演化历史,通过帮助读者理解 Linux 内核对 I/O 模块的优化改进思路,相信不仅仅是让读者了解 Linux 底层系统的设计原理,更能对读者们在以后优化改进自己的程序设计过程中能够有所启发。
static int ep_remove(struct eventpoll *ep, struct epitem *epi) { unsigned long flags; struct file *file = epi->ffd.file; /* * Removes poll wait queue hooks. We _have_ to do this without holding * the "ep->lock" otherwise a deadlock might occur. This because of the * sequence of the lock acquisition. Here we do "ep->lock" then the wait * queue head lock when unregistering the wait queue. The wakeup callback * will run by holding the wait queue head lock and will call our callback * that will try to get "ep->lock". */ ep_unregister_pollwait(ep, epi); /* Remove the current item from the list of epoll hooks */ spin_lock(&file->f_lock); if (ep_is_linked(&epi->fllink)) list_del_init(&epi->fllink); spin_unlock(&file->f_lock); rb_erase(&epi->rbn, &ep->rbr); spin_lock_irqsave(&ep->lock, flags); if (ep_is_linked(&epi->rdllink)) list_del_init(&epi->rdllink); spin_unlock_irqrestore(&ep->lock, flags); /* At this point it is safe to free the eventpoll item */ kmem_cache_free(epi_cache, epi); atomic_long_dec(&ep->user->epoll_watches); return 0; }
/* * Modify the interest event mask by dropping an event if the new mask * has a match in the current file status. Must be called with "mtx" held. */ static int ep_modify(struct eventpoll *ep, struct epitem *epi, struct epoll_event *event) { int pwake = 0; unsigned int revents; poll_table pt; init_poll_funcptr(&pt, NULL); /* * Set the new event interest mask before calling f_op->poll(); * otherwise we might miss an event that happens between the * f_op->poll() call and the new event set registering. */ epi->event.events = event->events; pt._key = event->events; epi->event.data = event->data; /* protected by mtx */ /* * Get current event bits. We can safely use the file* here because * its usage count has been increased by the caller of this function. */ revents = epi->ffd.file->f_op->poll(epi->ffd.file, &pt); /* * If the item is "hot" and it is not registered inside the ready * list, push it inside. */ if (revents & event->events) { spin_lock_irq(&ep->lock); if (!ep_is_linked(&epi->rdllink)) { list_add_tail(&epi->rdllink, &ep->rdllist); /* Notify waiting tasks that events are available */ if (waitqueue_active(&ep->wq)) wake_up_locked(&ep->wq); if (waitqueue_active(&ep->poll_wait)) pwake++; } spin_unlock_irq(&ep->lock); } /* We have to call this outside the lock */ if (pwake) ep_poll_safewake(&ep->poll_wait); return 0; }
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 10760 user 20 0 3061604 84832 5956 S 82.4 0.6 126:47.61 Process 29424 user 20 0 54060 2668 1360 R 17.6 0.0 0:00.03 **top**
自愿上下文切换变多了,说明进程都在等待资源,有可能发生了 I/O 等其他问题。 非自愿上下文切换变多了,说明进程都在被强制调度,也就是都在争抢 CPU,说明 CPU 的确成了瓶颈。 中断次数变多了,说明 CPU 被中断处理程序占用,还需要通过查看/proc/interrupts文件来分析具体的中断类型。
CPU 使用率
除了系统负载、上下文切换信息,最直观的 CPU 问题指标就是 CPU 使用率信息。Linux 通过/proc虚拟文件系统向用户控件提供系统内部状态信息,其中/proc/stat则是 CPU 和任务信息统计。
/** * enum irqreturn * @IRQ_NONE: interrupt was not from this device * @IRQ_HANDLED: interrupt was handled by this device * @IRQ_WAKE_THREAD: handler requests to wake the handler thread */ enumirqreturn { IRQ_NONE, IRQ_HANDLED, IRQ_WAKE_THREAD, }; typedefenumirqreturnirqreturn_t; #define IRQ_RETVAL(x) ((x) != IRQ_NONE)
注销中断处理函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/** * free_irq - free an interrupt allocated with request_irq * @irq: Interrupt line to free * @dev_id: Device identity to free * * Remove an interrupt handler. The handler is removed and if the * interrupt line is no longer in use by any driver it is disabled. * On a shared IRQ the caller must ensure the interrupt is disabled * on the card it drives before calling this function. The function does * not return until any executing interrupts for this IRQ have completed. * This function must not be called from interrupt context. */ voidfree_irq(unsignedint irq, void *dev_id);
voidraise_softirq(unsignedint nr) { unsignedlong flags; local_irq_save(flags); raise_softirq_irqoff(nr); local_irq_restore(flags); } /* This function must run with irqs disabled */ inlinevoidrasie_softirq_irqsoff(unsignedint nr) { __raise_softirq_irqoff(nr); /* If we're in an interrupt or softirq, we're done * (this also catches softirq-disabled code). We will * actually run the softirq once we return from the irq * or softirq. * Otherwise we wake up ksoftirqd to make sure we * schedule the softirq soon. */ if (! in_interrupt()) /* 如果不处于硬中断或软中断 */ wakeup_softirqd(void); /* 唤醒ksoftirqd/n进程 */ }
staticvoidwakeup_softirqd(void) { /* Interrupts are disabled: no need to stop preemption */ structtask_struct *tsk = __get_cpu_var(ksoftirqd); if (tsk && tsk->state != TASK_RUNNING) wake_up_process(tsk); }
/* We restart softirq processing MAX_SOFTIRQ_RESTART times, * and we fall back to softirqd after that. * This number has been established via experimentation. * The two things to balance is latency against fairness - we want * to handle softirqs as soon as possible, but they should not be * able to lock up the box. */ asmlinkage void __do_softirq(void) { structsoftirq_action *h; __u32 pending; /* 本函数能重复触发执行的次数,防止占用过多的cpu时间 */ int max_restart = MAX_SOFTIRQ_RESTART; int cpu; pending = local_softirq_pending(); /* 激活的软中断位图 */ account_system_vtime(current); /* 本地禁止当前的软中断 */ __local_bh_disable((unsignedlong)__builtin_return_address(0), SOFTIRQ_OFFSET); lockdep_softirq_enter(); /* current->softirq_context++ */ cpu = smp_processor_id(); /* 当前cpu编号 */ restart: /* Reset the pending bitmask before enabling irqs */ set_softirq_pending(0); /* 重置位图 */ local_irq_enable(); h = softirq_vec; do { if (pending & 1) { unsignedint vec_nr = h - softirq_vec; /* 软中断索引 */ int prev_count = preempt_count(); kstat_incr_softirqs_this_cpu(vec_nr); trace_softirq_entry(vec_nr); h->action(h); /* 调用软中断的处理函数 */ trace_softirq_exit(vec_nr); if (unlikely(prev_count != preempt_count())) { printk(KERN_ERR "huh, entered softirq %u %s %p""with preempt_count %08x," "exited with %08x?\n", vec_nr, softirq_to_name[vec_nr], h->action, prev_count, preempt_count()); } rcu_bh_qs(cpu); } h++; pending >>= 1; } while(pending); local_irq_disable(); pending = local_softirq_pending(); if (pending & --max_restart) /* 重复触发 */ goto restart; /* 如果重复触发了10次了,接下来唤醒ksoftirqd/n内核线程来处理 */ if (pending) wakeup_softirqd(); lockdep_softirq_exit(); account_system_vtime(current); __local_bh_enable(SOFTIRQ_OFFSET); }
后来又针对某些需要回应eoi(end of interrupt)的中断控制器,加入了fast eoi type,针对smp加入了per cpu type。把这些不同的中断类型抽象出来后,成为了中断子系统的流控层。要使所有的体系架构都可以重用这部分的代码,中断控制器也被进一步地封装起来,形成了中断子系统中的硬件封装层。我们可以用下面的图示表示通用中断子系统的层次结构:
这种情况下,栈无疑提供很好的解决办法。一、对于通用寄存器传参的冲突,我们可以再调用子函数前,将通用寄存器临时压入栈中;在子函数调用完毕后,在将已保存的寄存器再弹出恢复回来。二、而局部变量的空间申请,也只需要向下移动下栈顶指针;将栈顶指针向回移动,即可就可完成局部变量的空间释放;三、对于函数的返回,也只需要在调用子函数前,将返回地址压入栈中,待子函数调用结束后,将函数返回地址弹出给 PC 指针,即完成了函数调用的返回;
然而栈的意义还不只是函数调用,有了它的存在,才能构建出操作系统的多任务模式。我们以 main 函数调用为例,main 函数包含一个无限循环体,循环体中先调用 A 函数,再调用 B 函数。
1 2 3 4 5 6 7 8 9
func B(): return;
func A(): B();
func main(): while (1) A();
试想在单处理器情况下,程序将永远停留在此 main 函数中。即使有另外一个任务在等待状态,程序是没法从此 main 函数里面跳转到另一个任务。因为如果是函数调用关系,本质上还是属于 main 函数的任务中,不能算多任务切换。此刻的 main 函数任务本身其实和它的栈绑定在了一起,无论如何嵌套调用函数,栈指针都在本栈范围内移动。
由此可以看出一个任务可以利用以下信息来表征:
main 函数体代码
main 函数栈指针
当前 CPU 寄存器信息
假如我们可以保存以上信息,则完全可以强制让出 CPU 去处理其他任务。只要将来想继续执行此 main 任务的时候,把上面的信息恢复回去即可。有了这样的先决条件,多任务就有了存在的基础,也可以看出栈存在的另一个意义。在多任务模式下,当调度程序认为有必要进行任务切换的话,只需保存任务的信息(即上面说的三个内容)。恢复另一个任务的状态,然后跳转到上次运行的位置,就可以恢复运行了。
可见每个任务都有自己的栈空间,正是有了独立的栈空间,为了代码重用,不同的任务甚至可以混用任务的函数体本身,例如可以一个main函数有两个任务实例。至此之后的操作系统的框架也形成了,譬如任务在调用 sleep() 等待的时候,可以主动让出 CPU 给别的任务使用,或者分时操作系统任务在时间片用完是也会被迫的让出 CPU。不论是哪种方法,只要想办法切换任务的上下文空间,切换栈即可。
而 ARM 上中断栈和内核栈则是共享的;中断栈和内核栈共享有一个负面因素,如果中断发生嵌套,可能会造成栈溢出,从而可能会破坏到内核栈的一些重要数据,所以栈空间有时候难免会捉襟见肘。
## Linux 为什么需要区分这些栈? 为什么需要区分这些栈,其实都是设计上的问题。这里就我看到过的一些观点进行汇总,供大家讨论:
## 为什么需要单独的进程内核栈?
所有进程运行的时候,都可能通过系统调用陷入内核态继续执行。假设第一个进程 A 陷入内核态执行的时候,需要等待读取网卡的数据,主动调用 schedule() 让出 CPU;此时调度器唤醒了另一个进程 B,碰巧进程 B 也需要系统调用进入内核态。那问题就来了,如果内核栈只有一个,那进程 B 进入内核态的时候产生的压栈操作,必然会破坏掉进程 A 已有的内核栈数据;一但进程 A 的内核栈数据被破坏,很可能导致进程 A 的内核态无法正确返回到对应的用户态了; 为什么需要单独的线程栈?
Linux 调度程序中并没有区分线程和进程,当调度程序需要唤醒”进程”的时候,必然需要恢复进程的上下文环境,也就是进程栈;但是线程和父进程完全共享一份地址空间,如果栈也用同一个那就会遇到以下问题。假如进程的栈指针初始值为 0x7ffc80000000;父进程 A 先执行,调用了一些函数后栈指针 esp 为 0x7ffc8000FF00,此时父进程主动休眠了;接着调度器唤醒子线程 A1: 此时 A1 的栈指针 esp 如果为初始值 0x7ffc80000000,则线程 A1 一但出现函数调用,必然会破坏父进程 A 已入栈的数据。 如果此时线程 A1 的栈指针和父进程最后更新的值一致,esp 为 0x7ffc8000FF00,那线程 A1 进行一些函数调用后,栈指针 esp 增加到 0x7ffc8000FFFF,然后线程 A1 休眠;调度器再次换成父进程 A 执行,那这个时候父进程的栈指针是应该为 0x7ffc8000FF00 还是 0x7ffc8000FFFF 呢?无论栈指针被设置到哪个值,都会有问题不是吗? 进程和线程是否共享一个内核栈?
int n,m; //系统中进程总数n和资源种类总数m int Available[1..m]; //资源当前可用总量 int Allocation[1..n,1..m]; //当前给分配给每个进程的各种资源数量 int Need[1..n,1..m];//当前每个进程还需分配的各种资源数量 int Work[1..m]; //当前可分配的资源 bool Finish[1..n]; //进程是否结束
1 2
### 安全判定算法 初始化
Work = Available(动态记录当前剩余资源) Finish[i] = false(设定所有进程均未完成)
1 2
查找可执行进程Pi(未完成但目前剩余资源可满足其需要,这样的进程是能够完成的)
Finish[i] = false Need[i] <= Work
1 2 3
如果没有这样的进程Pi,则跳转到第4步
(若有则)Pi一定能完成,并归还其占用的资源,即:
Finish[i] = true Work = Work +Allocation[i] GOTO 第2步,继续查找
/* This struct defines a memory VMM memory area. */ structvm_area_struct { structmm_struct * vm_mm;/* VM area parameters */ unsignedlong vm_start; unsignedlong vm_end; /* linked list of VM areas per task, sorted by address */ structvm_area_struct *vm_next; pgprot_t vm_page_prot; unsignedlong vm_flags; /* AVL tree of VM areas per task, sorted by address */ short vm_avl_height; structvm_area_struct * vm_avl_left; structvm_area_struct * vm_avl_right; /* For areas with an address space and backing store, vm_area_struct *vm_next_share; struct vm_area_struct **vm_pprev_share; struct vm_operations_struct * vm_ops; unsigned long vm_pgoff; /* offset in PAGE_SIZE units, not PAGE_CACHE_SIZE */ structfile * vm_file; unsignedlong vm_raend; void * vm_private_data; /* was vm_pte (shared mem) */ };
Mark_Accessed(b) { if b.state==(UNACTIVE && UNREFERENCE) b.state = REFERENCE elseif b.state == (UNACTIVE && REFERENCE) { b.state = (ACTIVE && UNREFERENCE) Add X to tail of active_list } elseif b.state == (ACTIVE && UNREFERENCE) b.state = (ACTIVE && REFERENCE) } Reclaim() { if active_list not empty and scan_num<MAX_SCAN1 { X = head of active_list if (X.state & REFERENCE) == 0 Add X to tail of inactive_list else { X.state &= ~REFERENCE Move X to tail of active_list } scan_num++ } scan_num = 0 if inactive_list not emptry and scan_num < MAX_SCAN2 { X = head of inactive_list if (X.state & REFERENCE) == 0 return X else { X.state = ACTIVE | UNREFERENCE Move X to tail of active_list } scan_num++ } returnNULL } Access(b){ if b is not in cache { if slot X free put b into X else { X=Reclaim() put b into X } Add X to tail of inactive_list } Mark_Accessed(X) }
unix提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。 但这样就导致了问题,如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
struct kmem_cache { struct array_cache *array[NR_CPUS];//per_cpu数据,记录了本地高速缓存的信息,也是用于跟踪最近释放的对象,每次分配和释放都要直接访问它。 unsigned int batchcount;//本地高速缓存转入和转出的大批数据数量 unsigned int limit;//本地高速缓存中空闲对象的最大数目 unsigned int shared;
unsigned int buffer_size;/*buffer的大小,就是对象的大小*/ u32 reciprocal_buffer_size;
unsigned int flags; /* constant flags */ unsigned int num; /* ## of objs per slab *//*slab中有多少个对象*/
/* order of pgs per slab (2^n) */ unsigned int gfporder;/*每个slab中有多少个页*/
gfp_t gfpflags; /*与伙伴系统交互时所提供的分配标识*/
size_t colour; /* cache colouring range *//*slab中的着色*/ unsigned int colour_off; /* colour offset */着色的偏移量 struct kmem_cache *slabp_cache; unsigned int slab_size; //slab管理区的大小 unsigned int dflags; /* dynamic flags */
/* 6) statistics */ //一些用于调试用的变量 #ifdef CONFIG_DEBUG_SLAB unsigned long num_active; unsigned long num_allocations; unsigned long high_mark; unsigned long grown; unsigned long reaped; unsigned long errors; unsigned long max_freeable; unsigned long node_allocs; unsigned long node_frees; unsigned long node_overflow; atomic_t allochit; atomic_t allocmiss; atomic_t freehit; atomic_t freemiss;
int obj_offset; int obj_size; #endif /* CONFIG_DEBUG_SLAB */ //用于组织该高速缓存中的slab struct kmem_list3 *nodelists[MAX_NUMNODES];/*最大的内存节点*/
struct kmem_list3 { /*三个链表中存的是一个高速缓存slab*/ /*在这三个链表中存放的是cache*/ struct list_head slabs_partial; //包含空闲对象和已经分配对象的slab描述符 struct list_head slabs_full;//只包含非空闲的slab描述符 struct list_head slabs_free;//只包含空闲的slab描述符 unsigned long free_objects; /*高速缓存中空闲对象的个数*/ unsigned int free_limit; //空闲对象的上限 unsigned int colour_next; /* Per-node cache coloring *//*即将要着色的下一个*/ spinlock_t list_lock; struct array_cache *shared; /* shared per node */ struct array_cache **alien; /* on other nodes */ unsigned long next_reap; /* updated without locking *//**/ int free_touched; /* updated without locking */ };
接下来介绍描述单个slab的结构struct slab
1 2 3 4 5 6 7 8
struct slab { struct list_head list; //用于将slab连入keme_list3的链表 unsigned long colouroff; //该slab的着色偏移 void *s_mem; /* 指向slab中的第一个对象*/ unsigned int inuse; /* num of objs active in slab */已经分配出去的对象 kmem_bufctl_t free; //下一个空闲对象的下标 unsigned short nodeid; //节点标识符 };
struct array_cache { unsigned int avail;/*当前cpu上有多少个可用的对象*/ unsigned int limit;/*per_cpu里面最大的对象的个数,当超过这个值时,将对象返回给伙伴系统*/ unsigned int batchcount;/*一次转入和转出的对象数量*/ unsigned int touched;/*标示本地cpu最近是否被使用*/ spinlock_t lock;/*自旋锁*/ void *entry[]; /* * Must have this definition in here for the proper * alignment of array_cache. Also simplifies accessing * the entries. */ };
还有构造函数修改了对象里面的值,至于为什么构造函数会出现这么多次,可能是因为,这个函数被注册了之后,系统的其他地方也会调用这个函数。在这里可以分析源码,当调用keme_cache_create()的时候是没有调用对象的构造函数的,调用kmem_cache_create()并没有分配slab,而是在创建对象的时候发现没有空闲对象,在分配对象的时候,会调用构造函数初始化对象。 另外结合上面的代码可以发现,alloc three val是在kmem_cache_free之后打印的,但是它的值依然可以被打印出来,这充分说明了,slab这种机制是在将某个对象使用完之后,就其缓存起来,它还是切切实实的存在于内存中。 再结合/proc/slabinfo的信息看我们自己创建的slab高速缓存
理想情况下,只要满足“出现了优先级更高的进程”这个条件,当前进程就应该被立刻抢占。但是,就像多线程程序需要用锁来保护临界区资源一样,内核中也存在很多这样的临界区,不大可能随时随地都能接收抢占。 linux 2.4时的设计就非常简单,内核不支持抢占。进程运行在内核态时(比如正在执行系统调用、正处于异常处理函数中),是不允许抢占的。必须等到返回用户态时才会触发调度(确切的说,是在返回用户态之前,内核会专门检查一下是否需要调度); linux 2.6则实现了内核抢占,但是在很多地方还是为了保护临界区资源而需要临时性的禁用内核抢占。
虽然瘦内核方法有自己的优势(硬实时支持与标准Linux内核共存),但这种方法也有缺点。实时任务和非实时任务是独立的,这造成了调试困难。而且,非实时任务并未得到Linux平台的完全支持(瘦内核之所以称为瘦的一个原因)。使用这种方法的例子有RTLinux(现在由Wind River Systems专有),实时应用程序接口(RTAI)和Xenomai。
void disable_irq(unsigned int irq); void disable_irq_nosync(unsigned int irq); void enable_irq(unsigned int irq); void synchronise_irq(unsigned int irq);
msgsnd and msgrcv are never automatically restarted after being interrupted by a signal handler, regardless of the setting of the SA_RESTART flag when establishing a signal handler.
BSS(Block Started by Symbol,以符号开始的块)一词最初是UA-SAP汇编器(United Aircraft Symbolic Assembly Program)中的伪指令,用于为符号预留一块内存空间。该汇编器由美国联合航空公司于20世纪50年代中期为IBM 704大型机所开发。
使用readelf -l可以显示文件的program header信息。我们对sleep.o执行readelf -l sleep.o,会输出There are no program headers in this file.。 program header和文件中的segment一一对应,因为目标代码文件中没有segment,program header也就没有必要了。
/* * This structure defines the functions that are used to load the binary formats that * linux accepts. */ struct linux_binfmt { struct linux_binfmt * next; struct module *module; int (*load_binary)(struct linux_binprm *, struct pt_regs * regs); int (*load_shlib)(struct file *) int (*core_dump)(long signr, struct pt_regs * regs, struct file * file); unsigned long min_coredump; /* minimal dump size */ int hasvdso; };
源码树下的Makeconfig文件中有许多用于特定目的的变量,你可以在编译目录下创建一个configparms文件来改写这些变量。执行make命令的时候configparms文件中的内容将会按照Makefile规则进行解析。比如可以通过在其中设置 CFLAGS LDFLAGS 环境变量来优化编译,设置 CC BUILD_CC AR RANLIB 来指定交叉编译环境。
—enable-static-nss 编译静态版本的NSS(Name Service Switch)库。仅在/etc/nsswitch.conf中只使用dns和files的情况下,NSS才能编译成静态库,并且你还需要在静态编译应用程序的时候明确的连接所有与NSS库相关的库才行[比如:gcc -static test.c -o test -Wl,-lc,-lnss_files,-lnss_dns,-lresolv]。不推荐使用此选项,因为连接到静态NSS库的程序不能动态配置以使用不同的名字数据库。
链接器会为对外部符号的引用修改为正确的被引用符号的地址,当无法为引用的外部符号找到对应的定义时,链接器会报undefined reference to XXXX的错误。另外一种情况是,找到了多个符号的定义,这种情况链接器有一套规则。在描述规则前需要了解强符号和弱符号的概念,简单讲函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号。
(gdb)bt #0 0x00007fe0ca83518e in _IO_vfprintf_internal ( s=0x7fe0cabad620 <_IO_2_1_stdout_>, format=0x7fe0cabb26dd "ptr is %p\n", ap=ap@entry=0x7ffcbd652058) at vfprintf.c:1267 #1 0x00007fe0ca83d899 in __printf (format=<optimised out>) at printf.c:33 #2 0x00007fe0cabb26cc in malloc () from ./mymalloc.so #3 0x00007fe0ca8551d5 in __GI__IO_file_doallocate ( fp=0x7fe0cabad620 <_IO_2_1_stdout_>) at filedoalloc.c:127 #4 0x00007fe0ca863594 in __GI__IO_doallocbuf ( fp=fp@entry=0x7fe0cabad620 <_IO_2_1_stdout_>) at genops.c:398 #5 0x00007fe0ca8628f8 in _IO_new_file_overflow ( f=0x7fe0cabad620 <_IO_2_1_stdout_>, ch=-1) at fileops.c:820 #6 0x00007fe0ca86128d in _IO_new_file_xsputn ( f=0x7fe0cabad620 <_IO_2_1_stdout_>, data=0x7fe0cabb26dd, n=7) at fileops.c:1331 #7 0x00007fe0ca835241 in _IO_vfprintf_internal (
uint32_tfoo(uint32_t lo, uint32_t hi) { uint32_t sum = 0; if (lo < hi) { uint32_t n = hi - lo; if ((hi & 1) != 0) { for (uint32_t i = 0; i < n; i++) { sum += lo * 2; // folded into lea sum += i * 2; // folded into lea } } else { uint32_t last_i; for (uint32_t i = 0; i < n; i++) { sum += lo; last_i = lo; lo++; } gLastI = last_i; } } return sum; }
uint32_tfoo(uint32_t lo, uint32_t hi) { uint32_t sum = 0; if (lo < hi) { if ((hi & 1) == 0) { for (uint32_t i = lo; i < hi; i++) { sum += i; } gLastI = hi - 1; } else { for (uint32_t i = lo; i < hi; i++) { uint32_t y = 2 * i; sum += y; } } } return sum; }
OpenMP是一种通用的并行程序设计语言,其通过在源代码中添加编译制导语句,提示编译器如何进行程序的并行化。OpenMP具有书写方便,不需要改变源代码结构等多种优点。Intel的编译器支持OpenMP。本次程序并不打算使用OpenMP进行并行化,而打算使用Windows Thread。但是由于本程序需要使用到Intel Math Kernel Library,而Intel Math Kernel Library中的代码支持OpenMP并行化。所以有必要使用一些基本的OpenMP设置函数。
Intel编译器提示,内部的3个循环都进行了向量化。此时出现了令人惊喜的成绩。程序的执行时间突然降到了1.453秒。使用VTune进行分析,发现Intel编译器对于开根号倒数的计算自动调用了内部的向量化代码库。注意此时,还没有使用Intel Math Kernel Library,所以这个向量代码库是Intel编译器内置的,虽然效率没有使用Intel Math Kernel Library高,但是速度已经提高了很多。
使用Intel Math Kernel Library
Intel Math Kernel Library中提供了一部分的向量函数(Vector Mathematical Functions)。这类函数提供了对于普通数学计算函数的快速的向量化计算。VML中有一个向量函数就是计算开根号倒数的。
for ( i = 2; i < NPARTS; i ++ ) { for ( j = 0; j < i - 1; j ++ ) { dist[j] = (r[0][j] - r[0][i]) * (r[0][j] - r[0][i]) + (r[1][j] - r[1][i]) * (r[1][j] - r[1][i]) + (r[2][j] - r[2][i]) * (r[2][j] - r[2][i]); } vdInvSqrt(i-1, dist, dist); for ( j = 0; j < i - 1; j ++ ) { pot += dist[j]; } }
优化后出现了令人可惜可贺的成绩:0.796秒。
根据Cache大小优化Intel Math Kernel Library调用
在上面的程序中对于MKL函数的调用是每次内部循环都执行一次调用,我们知道每次执行函数的调用都是需要开销的,那是否有更优化的调用MKL方法那?下面这段话摘自Intel Math Kernel Library的说明文档上:
There are two extreme cases: so-called “short” and “long” vectors (logarithmic scale is used to show both cases). For short vectors there are cycle organization and initialization overheads. The cost of such overheads is amortized with increasing vector length, and for vectors longer than a few dozens of elements the performance remains quite flat until the L2 cache size is exceeded with the length of the vector.
voidinitPositions() { int i, j; for( i = 0; i < DIMS; i ++ ) for( j = 0; j < NPARTS; j ++ ) r[i][j] = (double) rand(); }
voidupdatePositions() { int i, j; for( i = 0; i < DIMS; i ++ ) for( j = 0; j < NPARTS; j ++ ) r[i][j] -= (double) rand(); }
在main函数中:
1 2 3 4
pot = 0.0; computePot(); pot*=(double)RAND_MAX; if (i%10 == 0) printf("%5d: Potential: %20.7f\n", i, pot);
其次需要进行updatePositions内rand函数的优化。虽然rand函数本身的执行时间非常短,但是其频繁得进行调用却影响了性能。通过查找Microsoft Visual Studio .NET 2005中提供的源代码。将其中的rand函数提取出来,进行必要的修改,并且加上inline属性。从而加快程序的调用速度。具体代码如下:
floatInvSqrt(float x){ float xhalf = 0.5f*x; int i = *(int*)&x; // get bits for floating value i = 0x5f3759df - (i>>1); // gives initial guess y0 x = *(float*)&i; // convert bits back to float x = x*(1.5f-xhalf*x*x); // Newton step, repeating increases accuracy return x; }
doublemyinvsqrt(double x) { double xhalf = 0.5*x; __int64 i = *(__int64*)&x; i = 0x5fe6ec85e7de30da - (i>>1); x = *(double*)&i; x = x*(1.5-xhalf*x*x); x = x*(1.5-xhalf*x*x); x = x*(1.5-xhalf*x*x); x = x*(1.5-xhalf*x*x); return x; }
doublemyinvsqrt(double x) { double xhalf = 0.5*x; __int64 i = *(__int64*)&x; i = newmagicnum - (i>>1); x = *(double*)&i; x = x*(1.5-xhalf*x*x); x = x*(1.5-xhalf*x*x); x = x*(1.5-xhalf*x*x); x = x*offset return x; }
在进行优化前,还有一点需要注意的是。rsqrtps函数是4个元素一算的,所以本程序使用4个元素作为一次计算单元来向量化。而用户输入的数据并不可能是正好4个元素。对于Intel编译器以及VML函数库来所,其使用的解决方法称为” Strip-mining and Cleanup”。即先按照4个数据一组进行计算。对于剩下的个别数据再进行单独计算。这对于通用化的程序来说是必须的。但是在我们的程序中,多计算几个并不会影响结果。而对于单独几个的数据如果另外处理不但会增加程序设计的复杂性,而且性能也可能会降低。所以本程序使用过渡计算的方法。即对于需要计算的数据中不足4个的,补满4个将其后面的数据计算掉。但是此时需要注意,由于dist变量是全局变量,默认的值为全0。如果过渡计算遇到0的值,速度可能会受到影响。所以本程序需要在一开始,将会被过渡计算使用到,但是从来不会被初始化的存储单元,初始化成1。具体代码如下:
判断传入指针参数的类型的字节大小(getTypeStoreSize)是否超过指定值。如果超过,则返回false。比如,在本例中,对第一个输入向量指针参数<32 x i8> addrspace(4)* %in,getTypeID()返回PointerTyID,getPointerElementType()返回指向VectorTyID类型的指针,getTypeStoreSize()返回类型的保存大小,这个值由向量中元素的数量(getNumElements=32)和每个向量元素大小(getTypeSizeInBits=8bits)的乘积决定。乘积单位为比特,需要转为字节。因此,针对<32 x i8> addrspace(4)* %in的getTypeStoreSize返回值为32字节。
// TODO: It might be useful for any out arguments, not just privates. if (!ArgTy || (ArgTy->getAddressSpace() != DL->getAllocaAddrSpace() && !AnyAddressSpace) || Arg.hasByValAttr() || Arg.hasStructRetAttr() || DL->getTypeStoreSize(ArgTy->getPointerElementType()) > MaxOutArgSizeBytes) { returnfalse; } return checkArgumentUses(Arg); }
其中,ReplVal是store指令的操作数(本例中为%r),ValVec是以ReturnInst为索引从Replacements中取得的ReplacementVec向量,该向量的单元是一对参数值<Argument *, Value *>。遍历ReplaceableStores的目的就是向ValVec中写入参数和值,即:
intmain(int argc, char *argv[]) { sig_register(); int a = 10, b = -2, c = 100; int d = 100; printf("max among 10, -2 and 100 is %d.\n", max(a, b, c)); return0; }
GNU CC(简称gcc)是GNU项目中符合ANSI C标准的编译系统,能够编译用C、C++、Object C、Jave等多种语言编写的程序。gcc又可以作为交叉编译工具,它能够在当前CPU平台上为多种不同体系结构的硬件平台开发软件,非常适合在嵌入式领域的开发编译,如常用的arm-linux-gcc交叉编译工具
-include用来包含头文件,但一般情况下包含头文件都在源码里用#i nclude xxxxxx实现,-include参数很少用。-I参数是用来指定头文件目录,/usr/include目录一般是不用指定的,gcc知道去那里找,但 是如果头文件不在/usr/icnclude里我们就要用-I参数指定了,比如头文件放在/myinclude目录里,那编译命令行就要加上-I/myinclude参数了,如果不加你会得到一个”xxxx.h: No such file or directory”的错误。-I参数可以用相对路径,比如头文件在当前目录,可以用-I.来指定。上面我们提到的—cflags参数就是用来生成-I参数的。
从源代码安装过软件的朋友一定对 ./configure && make && make install 安装三步曲非常熟悉了。然而究竟这个过程中的每一步幕后都发生了些什么呢?本文将带领你一探究竟。深入理解这个过程将有助于你在LFS的基础上玩出自己的花样来。不过需要说明的是本文对 Makefile 和 make 的讲解是相当近视和粗浅的,但是对于理解安装过程来说足够了。
用一句话来概括Makefile 与 make 的关系就是: Makefile 包含了所有的规则和目标,而 make 则是为了完成目标而去解释 Makefile 规则的工具。
make 语法
首先看看 make 的命令行语法:
make [options] [targets] [VAR=VALUE]... [options]是命令行选项,可以用 make —help 命令查看全部,[VAR=VALUE]是在命令行上指定环境变量,这两个大家都很熟悉,将在稍后详细讲解。而[targets]是什么呢?字面的意思是”目标”,也就是希望本次 make 命令所完成的任务。凭经验猜测,这个[targets]大概可以用”check”,”install”之类(也就是常见的测试和安装命令)。但是它到底是个啥玩意儿?不带任何”目标”的 make 命令是什么意思?为什么在安装 LFS 工具链中的 Perl-5.8.8 软件包时会出现”make perl utilities”这样怪异的命令?要回答这些问题必须首先理解 Makefile 文件中的”规则”。
Makefile 规则
Makefile 规则包含了文件之间的依赖关系和更新此规则目标所需要的命令。
一个简单的 Makefile 规则是这样写的:
1 2
TARGET : PREREQUISITES COMMAND
TARGET 规则的目标。也就是可以被 make 使用的”目标”。有些目标可以没有依赖而只有动作(命令行),比如”clean”,通常仅仅定义一系列删除中间文件的命令。同样,有些目标可以没有动作而只有依赖,比如”all”,通常仅仅用作”终极目标”。
从中可以看出,make 与 make all 以及 make prog1 prog2 三条命令其实是等价的。而常用的 make check 和 make install 也找到了归属。同时我们也看到了 Makefile 中的各种变量是如何影响编译的。针对这个特定的 Makefile ,你甚至可以省略安装三步曲中的 make 命令而直接使用 make install 进行安装。
同样,为了使用自定义的编译参数编译 prog2 ,我们可以使用 make prog2 CFLAGS=”-O3 -march=athlon64” 或 CFLAGS=”-O3 -march=athlon64” && make -e prog2 命令达到此目的。
Makefile 惯例
下面是Makefile中一些约定俗成的目标名称及其含义:
all 编译整个软件包,但不重建任何文档。一般此目标作为默认的终极目标。此目标一般对所有源程序的编译和连接使用”-g”选项,以使最终的可执行程序中包含调试信息。可使用 strip 程序去掉这些调试符号。
clean 清除当前目录下在 make 过程中产生的文件。它不能删除软件包的配置文件,也不能删除 build 时创建的那些文件。
这些约定俗成的变量分为三类。第一类代表可执行程序的名字,例如 CC 代表编译器这个可执行程序;第二类代表程序使用的参数(多个参数使用空格分开),例如 CFLAGS 代表编译器执行时使用的参数(一种怪异的做法是直接在 CC 中包含参数);第三类代表安装目录,例如 prefix 等等,含义简单,下面只列出它们的默认值。
AR 函数库打包程序,可创建静态库.a文档。默认是"ar"。 AS 汇编程序。默认是"as"。 CC C编译程序。默认是"cc"。 CXX C++编译程序。默认是"g++"。 CPP C/C++预处理器。默认是"$(CC) -E"。 FC Fortran编译器。默认是"f77"。 PC Pascal语言编译器。默认是"pc"。 YACC Yacc文法分析器。默认是"yacc"。
-p, —print-data-base 命令执行之前,打印出 make 读取的 Makefile 的所有数据(包括规则和变量的值),同时打印出 make 的版本信息。如果只需要打印这些数据信息,可以使用 make -qp 命令。查看 make 执行前的预设规则和变量,可使用命令 make -p -f /dev/null 。
链接器和加载器的核心动作是重定位和代码修改。当编译器或汇编器产生一个目标代码文件时,它使用文件中定义的未重定位代码地址和数据地址来生成代码,对于其它地方定义的数据或代码通常就是 0。作为链接过程的一部分,链接器会修改目标代码以反映实际分配的地址。例如,考虑如下这段将变量 a 中的内容通过寄存器 eax 移动到变量 b 的 x86 代码片段。
可执行程序中仍然有很多其它的 C 库例程,没有显示在这里,它们由启动代码和_write直接或间接的调用。由于可执行程序的文件格式不是可以重链接的,且操作系统从已知的固定位置加载它,因此它不包含重定位数据。它带有一个有助于调试器(debugger)工作的符号表,尽管这个程序没有使用这个符号表并且可以将其删除以节省空间。
每种 ABI 都通过将硬件定义的调用指令与内存、寄存器的使用约定组合起来定义了一个标准的过程调用序列。硬件的调用指令保存了返回地址(调用执行后的指令地址)并跳转到目标过程。在诸如 x86 这样具有硬件栈的体系结构中返回地址被压入栈中,而在其它体系结构中它会被保存在一个寄存器里,如果必要软件要负责将寄存器中的值保存在内存中。具有栈的体系结构通常都会有一个硬件的返回指令将返回地址推出栈并跳转到该地址,而其它体系结构则使用一个“跳转到寄存器中地址”的指令来返回。
在一个过程的内部,数据寻址可分为 4 类:
调用者可以向过程传递参数。
本地变量在过程中分配,并在过程返回前释放。
本地静态数据保存在内存的固定位置中,并为该过程私有。
全局静态数据保存在内存的固定位置中,并可被很多不同过程引用。
为每个过程调用分配的一块栈内存称为“栈框架(stack frame)”。
参数和本地变量通常在栈中分配空间,某一个寄存器可以作为栈指针,它可以基址寄存器来使用。SPARC 和 x86 中使用了该策略的一种比较普遍的变体,在一个过程开始的时候,会从栈指针中加载专门的框架指针或基址指针寄存器。这样就可以在栈中压入可变大小的对象,将栈指针寄存器中的值改变为难以预定的值。如果假定栈是从高地址向低地址生长的,而框架指针指向返回地址保存在内存中的位置,那么参数就位于框架指针较小的正偏移量处,本地变量在负偏移量处。由于操作系统通常会在程序启动前为其初始化栈指针,所以程序只需要在将输入压栈或推栈时更新寄存器即可。
嵌入式系统具有很小且分布怪异的地址空间。一个 64K 的地址空间可能会包括高速的片内 ROM 和 RAM,低速的片外 ROM 和 RAM,片内外围设备,或片外外围设备。也可能会存在多个不连续的 ROM 或 RAM 区域。嵌入式系统的链接器需要有办法来指明被链接程序在内存布局上的大量细节,分配特定类型的代码和数据,甚至将例程和变量分开放入特定的地址。
最简单的情况下,一个 a.out 文件包含一个小文件头,后面接着是可执行代码,然后是静态数据的初始值。后续型号为代码(称为指令空间 I)和数据(称为数据空间 D)提供了独立的地址空间,这样一个程序可以拥有 64K 的代码空间和 64K 的数据空间。为了支持这个特性,编译器、汇编器、链接器都被修改为可以创建两个段的目标文件(代码放入第一个段中,数据放入第二个段中,程序加载时先将第一个段载入进程的 I 空间,再将第二个段载入进程的 D 空间)。
独立的 I 和 D 空间还有另一个性能上的优势:由于一个程序不能修改自己的 I 空间,因此一个程序的多个实体可以共享一份程序代码的副本。在诸如 UNIX 这样的分时系统上,shell(命令解释器)和网络服务进程具有多个副本是很普遍的,共享程序代码可以节省相当可观的内存空间。
a.out 头部
a.out 的头部根据 UNIX 版本的不同而略有变化。
1 2 3 4 5 6 7 8
int a_magic; // 幻数 int a_text; // 文本段大小 int a_data; // 初始化的数据段大小 int a_bss; // 未初始化的数据段大小 int a_syms; // 符号表大小 int a_entry; // 入口点 int a_trsize; // 文本重定位段大小 int a_drsize; // 数据重定位段大小
char signature[2] = "MZ";//幻数 short lastsize; //最后一个块使用的字节数 short nblocks; //512 字节块的个数 short nreloc; //重定位项个数 short hdrsize; //以 16 字节段为单位的文件头部尺寸 short minalloc; //需额外分配的最小内存量 short maxalloc; //需额外分配的最大内存量 void far *sp; //初始栈指针 short checksum; //文件校验和 void far *ip; //初始指令指针short relocpos; //重定位修正表位置 short noverlay; //重叠的个数,程序为 0 char extra[]; //重叠所需的额外信息等 void far *relocs[]; //重定位项,从 relocpos 开始
int sh_name; //名称,可在字串表中索引到 int sh_type; //区段类型 int sh_flags; //标志位,见下 int sh_addr; //若可加载则为内存基址,否则为 0 int sh_offset; //区段起始点在文件中的位置 int sh_size; //区段大小(字节为单位) int sh_link; //相关信息对应的区段号,若没有则为 0 int sh_info; //区段相关的更多信息 int sh_align; //移动区段时的对齐粒度 int sh_entsize;//若该区段为一个表时其中表项的大小
int type; //类型:可加载代码或数据,动态链接信息,等 int offset; //段在文件中的偏移量 int virtaddr; //映射段的虚拟地址 int physaddr; //物理地址,未使用 int filesize; //文件中的段大小 int memsize; //内存中的段大小(如果包含 BSS 的话会更大些) int flags; //读,写,执行标志位 int align; //对齐要求,根据硬件页尺大小不同有变动
GNU 链接器是通过定义一个“link once”类型的区段(与公共块很相似)来解决这个模板的问题的。如果链接器看到诸如.gnu.linkonce.name 之类的区段名称,它会将第一个明确命名的此类区段保留下来并忽略其它冗余区段。同样编译器会将模板扩展到一个采用简化模板名称的.gnu.linkonce 区段中。
这种策略工作的相当不错,但它并不是万能的。例如,它不能保护功能上并不完全相同的 vtbl 和扩展模板。一些链接器尝试去检查被忽略的和保留的区段是否是每个字节都相同。这种方法是很保守的,但是如果两个文件采用了不同的优化选项,或编译器的版本不同,就会产生报错信息。另外,它也不能尽可能多的忽略冗余代码。在多数 C++系统中,所有的指针都具有相同的内部表示,这意味着一个模板的具有指向 int 类型指针参数的实例和指向float 类型指针参数的实例会产生相同的代码(即使它们的 C++数据类型不同)。某些链接器也尝试忽略那些和其它区段每个字节都相同的 link-once 区段,哪怕它们的名字并不是完全的相同,但这个问题仍然没有得到满意的解决。
预留名称的问题一直存在。在混合语言的程序中,情况甚至更糟,因为所有语言的代码都要避免使用任何其它语言运行时库中已经用到的名称。解决预留名称问题的方法之一是用其它东西(而不是过程调用)来调用运行时库。UNIX 系统采取的办法是修改 C 和 Fortran 过程的名称这样就不会因为疏忽而与库和其它例程中的名称冲突了。C 过程的名称通过在前面增加下划线来修饰,所以 main 就变成了_main。Fortran 的名称进一步被修改首尾各有一个下划线,所以 calc 就成了_calc_(这种独特的方法使得从 Fortran 中可以调用 C 中名字末尾带有下划线的例程,这样就可以用 C 编写 Fortran 的库)。
在其它系统上,编译器设计者们采取了截然相反的方法。多数汇编器和链接器允许在符号中使用 C 和 C++标识符中禁用的字符,如.或者$。运行库会使用带有禁用字符的名称来避免与应用程序的名称冲突,而不再是修改 C 或 fortran 程序中的名称。
C++类型编码:类型和范围
在一个 C++程序中,程序员可以定义很多具有相同名称但范围不同的函数和变量,对于函数,还有参数类型。一个单独的程序可以具有一个名为 V 的全局变量和一个类中的静态成员 C::V。C++允许函数名重载,即一些具有相同名称不同参数的函数,例如 f(int x)和 f(float x)。类的定义可以括入函数,括入重载名称,甚至括入重新定义了内嵌操作的函数,即一个类可以包含一个函数,它的名字实际上可以是>>或其它内建操作符。
UNIX 链接器和很多 Windows 链接器在命令行或者控制文件中会使用一种目标文件和库混合在一起的列表,然后依次处理,这样程序员就可以控制加载目标代码和搜索库的顺序了。虽然原则上这可以提供相当大的弹性并可以通过将同名私有例程列在库例程之前而在库例程中插入自己的私有同名例程,在实际中这种排序的搜索还可以提供一些额外的用处。程序员总是可以先列出所有他们自己的目标文件,然后是任何应用程序特定的库,然后是和数学、网络等相关的系统库,最后是标准系统库。
当程序员们使用多个库的时候,如果库之间存在循环依赖的时候经常需要将库列出多次。就是说,如果一个库 A 中的例程依赖一个库 B 中的例程,但是另一个库 B 中的例程又依赖了库 A 中的另一个例程,那么从 A 扫描到 B 或从 B 扫描到 A 都无法找到所有需要的例程。当这种循环依赖发生在三个或更多的库之间时情况会更加糟糕。告诉链接器去搜索 A B A 或者 B A B,甚至有时为 A B C D A B C D,这种方法看上去很丑陋,但是确实可以解决这个问题。
很多链接器将段重定位和符号重定位统一对待,这是因为它们将段当作是一种值为段基址的“伪符号”。这使得和段相关的重定位就成了和符号相关的重定位的特例。即使在将两种重定位统一对待的链接器中,此二者仍有一个重要区别:一个符号引用包括两个加数,即符号所在段的基值和符号在段内的偏移地址。有一些链接器在开始进入重定位阶段之前就会预先计算所有的符号地址,将段基址加到符号表中符号的值中。当每一项被重定位时会查找到段基址并相加。大多数情况下,并没有强制的理由要以这种或那种方法来进行这种操作。在少数链接器,尤其是那些针对实模式 x86 代码的链接器中,一个地址可以被重定位到和若干不同段相关的多个地址上,因此链接器只需要确定在上下文中一个特定引用的符号在特定段中的地址。
call .L2 ;; push PC in on the stack .L2: popl %ebx ;; PC into register EBX addl $_GLOBAL_OFFSET_TABLE_+[.-.L2],%ebx;; adjust ebx to GOT address
它存在一个对后面紧接着位置的call指令,这可以将 PC压入栈中而不用跳转,然后用pop指令将保存的 PC 加载到一个寄存器中并立刻加上call的目标地址和GOT地址之间的差。在一个由编译器生成的目标文件中,专门有一个针对addl指令操作数的R_386_GOTPC重定位项。它告诉链接器替换从当前指令到GOT基地址的偏移量,同时也是告诉链接器在输出文件中建立GOT的一个标记。在输出文件中,由于addl到GOT之间的距离是固定的,所以就不再需要重定位了。
特别在 x86 系统上,对于速度要求严格的任务,PIC 代码的性能降低是明显的,以至于某些系统对于共享库退而采用一种类似 PIC 的方法。
自举加载
在现代计算机中,计算机在硬件复位后运行的第一个程序总是存储在称为 bootstrap ROM 的随机只读存储器中。就像自己启动自己一样。当处理器上电或者复位后,它将寄存器复位为一致的状态。例如在 x86 系统中,复位序列跳转到系统地址空间顶部下面的 16 字节处。Bootstrap ROM 占用了地址空间顶端的 64K,然后这里的 ROM 代码就来启动计算机。在 IBM 兼容的 x86 系统上,引导 ROM 代码读取软盘上的第一个块,如果失败的话就读取硬盘上的第一个块,将它放置在内存位置 0,然后再跳转到位置 0。在第 0 块上的程序然后从磁盘上一个已知位置上加载另一个稍微大一些的操作系统引导程序到内存中,然后在跳转到这个程序,加载并运行操作系统。
为什么不直接加载操作系统?因为你无法将一个操作系统的引导程序放置在 512 个字节内。第一级引导程序只能从被引导磁盘的顶级目录中加载一个名字固定且大小不超过一个段的程序。操作系统引导程序具有更多的复杂代码如读取和解释配置文件,解压缩一个压缩的操作系统内核,寻址大量内存(在 x86 系统上的引导程序通常运行在实模式下,这意味着寻址 1MB 以上地址是比较复杂的)。完全的操作系统还要运行在虚拟内存系统上,可以加载需要的驱动程序,并运行用户级程序。很多 UNIX 系统使用一个近似的自举进程来运行用户台程序。内核创建一个进程,在其中装填一个只有几十个字节长度的小程序。然后这个小程序调用一个系统调用运行/etc/init 程序,这个用户模式的初始化程序然后依次运行系统所需要的各种配置文件,启动服务进程和登录程序。
共享库中最困难的就是地址空间管理。每一个共享库在使用它的程序里都占用一段固定的地址空间。不同的库,如果能够被使用在同一个程序中,它们还必须使用互不重叠的地址空间。虽然机械的检查库的地址空间是否重叠是可能的,但是给不同的库赋予相应的地址空间仍然是一种“魔法”。一方面,你还想在它们之间留一些余地,这样当其中某个新版本的库增长了一些时,它不会延伸到下一个库的空间而发生冲突。另一方面,你还想将你最常用的库尽可能紧密的放在一起以节省需要的页表数量(要知道在 x86 上,进程地址空间的每一个 4MB 的块都有一个对应的二级表)。
库之间的引用会稍微复杂一些。如果你正在创建,例如一个使用标准 C 库例程的共享数学库,那就要确保引用的正确。假定当链接器建立新库时需要用到的共享库中的例程已经建好,那么它只需要搜索该共享库的空占位库,就像普通的可执行程序引用共享库那样。这将让所有的引用都正确。只留下一个问题,就是需要有某种方法确保任何使用新库的程序也能够链接到旧库上。对新库的空占位库的适当设计可以确保这一点。
inline 函数存在一个相似的问题。通常,inline 函数被像宏那样扩展开,但是在某些情况下编译器会产生该函数相反的 out of line 版本。如果若干个不同的文件使用某个包含一个 inline 函数的单一头文件,并且某些文件需要一个 out of line 的版本,就会产生代码重复的相同问题。
对于全局的构造和析构代码,C++编译器在每一个输入文件中建立了完成构造和析构功能的例程。这些例程在逻辑上是匿名的,但是编译器给他们分配了可识别的名称。例如,GNU C++编译器会对名为junk的类中的变量创建名为_GLOBAL_.I.__4junk和_GLOBAL_.D.__4junk的构造例程及析构例程。在试验链接结束后,链接器驱动程序会检测输出文件的符号表并为全局构造和析构例程建立一个链表,这是通过编写一个由数组构成的队列的源代码文件来实现的(通过 C 或者汇编语言)。然后在再次链接中,C++的启动和退出代码使用这个数组中的内容去调用所有对应的例程。这和那些针对 C++的链接器的功能基本相同,区别仅仅是它是在链接器之外实现的。
GCC 所用的“仓库”实际上就是一个小的数据库。最终,工具开发者都会转而使用数据库来存储源代码和目标代码,就像 IBM 的 Viaual Age C++的 Montana 开发环境一样。数据库跟踪每一个声明和定义的位置,这样就可以在源代码改变后精确的指出哪些例程会对此修改具有依赖关系,并仅仅重新编译和链接那些修改了的地方。
简单地说,CPU 亲和性(affinity) 就是进程要在某个给定的 CPU 上尽量长时间地运行而不被迁移到其他处理器的倾向性。Linux 内核进程调度器天生就具有被称为 软 CPU 亲和性(affinity) 的特性,这意味着进程通常不会在处理器之间频繁迁移。这种状态正是我们希望的,因为进程迁移的频率小就意味着产生的负载小。
2.6 版本的 Linux 内核还包含了一种机制,它让开发人员可以编程实现 硬 CPU 亲和性(affinity)。这意味着应用程序可以显式地指定进程在哪个(或哪些)处理器上运行。
什么是 Linux 内核硬亲和性(affinity)?
在 Linux 内核中,所有的进程都有一个相关的数据结构,称为 task_struct。这个结构非常重要,原因有很多;其中与 亲和性(affinity)相关度最高的是 cpus_allowed 位掩码。这个位掩码由 n 位组成,与系统中的 n 个逻辑处理器一一对应。 具有 4 个物理 CPU 的系统可以有 4 位。如果这些 CPU 都启用了超线程,那么这个系统就有一个 8 位的位掩码。
如果为给定的进程设置了给定的位,那么这个进程就可以在相关的 CPU 上运行。因此,如果一个进程可以在任何 CPU 上运行,并且能够根据需要在处理器之间进行迁移,那么位掩码就全是 1。实际上,这就是 Linux 中进程的缺省状态。
/* This method will create threads, then bind each to its own cpu. */ bool do_cpu_stress(int numthreads) { int ret = TRUE; int created_thread = 0; /* We need a thread for each cpu we have... */ while ( created_thread < numthreads - 1 ) { int mypid = fork(); if (mypid == 0) /* Child process */ { printf("\tCreating Child Thread: #%i\n", created_thread); break; } else /* Only parent executes this */ { /* Continue looping until we spawned enough threads! */ ; created_thread++; } } /* NOTE: All threads execute code from here down! */
cpu_set_t mask; /* CPU_ZERO initializes all the bits in the mask to zero. */ CPU_ZERO( &mask ); /* CPU_SET sets only the bit corresponding to cpu. */ CPU_SET( created_thread, &mask ); /* sched_setaffinity returns 0 in success */ if( sched_setaffinity( 0, sizeof(mask), &mask ) == -1 ) { printf("WARNING: Could not set CPU Affinity, continuing...\n"); }
如果程序可以执行到这儿,那么我们的线程就已经设置了自己的亲和性(affinity)。调用 sched_setaffinity 会设置由 pid 所引用的进程的 CPU 亲和性(affinity)掩码。如果 pid 为 0,那么就使用当前进程。
void CPU_ZERO (cpu_set_t *set) 这个宏对 CPU 集 set 进行初始化,将其设置为空集。 void CPU_SET (int cpu, cpu_set_t *set) 这个宏将 cpu 加入 CPU 集 set 中。 void CPU_CLR (int cpu, cpu_set_t *set) 这个宏将 cpu 从 CPU 集 set 中删除。 int CPU_ISSET (int cpu, const cpu_set_t *set) 如果 cpu 是 CPU 集 set 的一员,这个宏就返回一个非零值(true),否则就返回零(false)。
对于本文来说,样例代码会继续让每个线程都执行某些计算量较大的操作。
清单 4. 每个线程都执行一个计算敏感的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/* Now we have a single thread bound to each cpu on the system */ int computation_res = do_cpu_expensive_op(41); cpu_set_t mycpuid; sched_getaffinity(0, sizeof(mycpuid), &mycpuid); if ( check_cpu_expensive_op(computation_res) ) { printf("SUCCESS: Thread completed, and PASSED integrity check!\n", mycpuid); ret = TRUE; } else { printf("FAILURE: Thread failed integrity check!\n", mycpuid); ret = FALSE; } return ret; }
我们使用一个 main 程序来封装这些方法,它使用一个用户指定的参数来说明要让多少个 CPU 繁忙。我们可以使用另外一个方法来确定系统中有多少个处理器:
#include<linux/init.h> #include<linux/module.h> #include<linux/kernel.h> MODULE_LICENSE("GPL"); staticintftrace_demo_init(void) { trace_printk("Can not see this in trace unless loaded for the second time\n"); return0; } staticvoidftrace_demo_exit(void) { trace_printk("Module unloading\n"); } module_init(ftrace_demo_init); module_exit(ftrace_demo_exit);
#include<sys/types.h> #include<sys/stat.h> intmain(int argc, char **argv) { int ret; int fd; int i = 0; char pidbuf[20]; pid_t id; id = fork(); if (id < 0) { fprintf(stderr, "Error in fork"); exit(-1); } elseif (id == 0) { scanf("%d", &i); ret = execv("hello", NULL); if (ret == -1) { fprintf(stderr, "Error in execv"); exit(-1); } } else { sprintf(pidbuf, "%ld", (long)id); fd = open("/sys/kernel/debug/tracing/set_ftrace_pid", O_CREAT | O_RDWR, 0660); if (fd < 0) { fprintf(stderr, "Error in open"); exit(-1); } write(fd, pidbuf, strlen(pidbuf)); close(fd); fd = open("/sys/kernel/debug/tracing/tracing_on", O_CREAT | O_RDWR, 0660); write(fd, "1", 2); close(fd); printf("!!!!\n"); sleep(5); } return0; }
然后使用ftrace进行追踪,可以得到一个system call的完整的结果。
Linux性能分析工具汇总
首先来看一张图:
上图是Brendan Gregg 的一次性能分析的分享,这里面的所有工具都可以通过 man 来获得它的帮助文档,下面简单介绍介绍一下常规的用法:
vmstat—虚拟内存统计
vmstat(VirtualMeomoryStatistics,虚拟内存统计)是 Linux 中监控内存的常用工具,可对操作系统的虚拟内存、进程、CPU 等的整体情况进行监视。vmstat 的常规用法:vmstat interval times 即每隔 interval 秒采样一次,共采样 times 次,如果省略 times,则一直采集数据,直到用户手动停止为止。简单举个例子:
[root@iz2ze76ybn73dvwmdij06zz ~]# cat monkey One day,a little monkey is playing by the well.一天,有只小猴子在井边玩儿. He looks in the well and shouts :它往井里一瞧,高喊道: “Oh!My god!The moon has fallen into the well!” “噢!我的天!月亮掉到井里头啦!” An older monkeys runs over,takes a look,and says,一只大猴子跑来一看,说, “Goodness me!The moon is really in the water!” “糟啦!月亮掉在井里头啦!” And olderly monkey comes over.老猴子也跑过来. He is very surprised as well and cries out:他也非常惊奇,喊道: “The moon is in the well.” “糟了,月亮掉在井里头了!” A group of monkeys run over to the well .一群猴子跑到井边来, They look at the moon in the well and shout:他们看到井里的月亮,喊道: “The moon did fall into the well!Come on!Let’get it out!” “月亮掉在井里头啦!快来!让我们把它捞起来!” Then,the oldest monkey hangs on the tree up side down ,with his feet on the branch . 然后,老猴子倒挂在大树上, And he pulls the next monkey’s feet with his hands.拉住大猴子的脚, All the other monkeys follow his suit,其他的猴子一个个跟着, And they join each other one by one down to the moon in the well. 它们一只连着一只直到井里. Just before they reach the moon,the oldest monkey raises his head and happens to see the moon in the sky,正好他们摸到月亮的时候,老猴子抬头发现月亮挂在天上呢 He yells excitedly “Don’t be so foolish!The moon is still in the sky!” 它兴奋地大叫:“别蠢了!月亮还好好地挂在天上呢!
直接查找符合条件的行
1 2 3 4 5 6 7 8 9
[root@iz2ze76ybn73dvwmdij06zz ~]# grep moon monkey “Oh!My god!The moon has fallen into the well!” “噢!我的天!月亮掉到井里头啦!” “Goodness me!The moon is really in the water!” “糟啦!月亮掉在井里头啦!” “The moon is in the well.” “糟了,月亮掉在井里头了!” They look at the moon in the well and shout:他们看到井里的月亮,喊道: “The moon did fall into the well!Come on!Let’get it out!” And they join each other one by one down to the moon in the well. Just before they reach the moon,the oldest monkey raises his head and happens to see the moon in the sky,正好他们摸到月亮的时候,老猴子抬头发现月亮挂在天上呢 He yells excitedly “Don’t be so foolish!The moon is still in the sky!”
查找反向符合条件的行
1 2 3 4 5 6 7 8 9 10 11 12 13 14
[root@iz2ze76ybn73dvwmdij06zz ~]# grep -v moon monkey One day,a little monkey is playing by the well.一天,有只小猴子在井边玩儿. He looks in the well and shouts :它往井里一瞧,高喊道: An older monkeys runs over,takes a look,and says,一只大猴子跑来一看,说, And olderly monkey comes over.老猴子也跑过来. He is very surprised as well and cries out:他也非常惊奇,喊道: A group of monkeys run over to the well .一群猴子跑到井边来, “月亮掉在井里头啦!快来!让我们把它捞起来!” Then,the oldest monkey hangs on the tree up side down ,with his feet on the branch . 然后,老猴子倒挂在大树上, And he pulls the next monkey’s feet with his hands.拉住大猴子的脚, All the other monkeys follow his suit,其他的猴子一个个跟着, 它们一只连着一只直到井里. 它兴奋地大叫:“别蠢了!月亮还好好地挂在天上呢!”
[root@iz2ze76ybn73dvwmdij06zz ~]# grep -i my monkey “Oh!My god!The moon has fallen into the well!” “噢!我的天!月亮掉到井里头啦!”
查找符合条件的行并输出行号
1 2 3 4 5 6 7 8 9
[root@iz2ze76ybn73dvwmdij06zz ~]# grep -n monkey monkey 1:One day,a little monkey is playing by the well.一天,有只小猴子在井边玩儿. 4:An older monkeys runs over,takes a look,and says,一只大猴子跑来一看,说, 6:And olderly monkey comes over.老猴子也跑过来. 9:A group of monkeys run over to the well .一群猴子跑到井边来, 13:Then,the oldest monkey hangs on the tree up side down ,with his feet on the branch . 15:And he pulls the next monkey’s feet with his hands.拉住大猴子的脚, 16:All the other monkeys follow his suit,其他的猴子一个个跟着, 19:Just before they reach the moon,the oldest monkey raises his head and happens to see the moon in the sky,正好他们摸到月亮的时候,老猴子抬头发现月亮挂在天上呢
查找开头是J的行
1 2
[root@iz2ze76ybn73dvwmdij06zz ~]# grep '^J' monkey Just before they reach the moon,the oldest monkey raises his head and happens to see the moon in the sky,正好他们摸到月亮的时候,老猴子抬头发现月亮挂在天上呢
查找结尾是呢的行
1 2
[root@iz2ze76ybn73dvwmdij06zz ~]# grep "呢$" monkey Just before they reach the moon,the oldest monkey raises his head and happens to see the moon in the sky,正好他们摸到月亮的时候,老猴子抬头发现月亮挂在天上呢
sed
sed是一种流编辑器,是一款处理文本比较优秀的工具,可以结合正则表达式一起使用。
sed执行过程:
sed命令: sed
语法 : sed [选项]… {命令集} [输入文件]…
常用命令:
d 删除选择的行
s 查找
y 替换
i 当前行前面插入一行
a 当前行后面插入一行
p 打印行
q 退出
替换符:
数字 :替换第几处
g : 全局替换
\1: 子串匹配标记,前面搜索可以用元字符集(..)
&: 保留搜索刀的字符用来替换其他字符
查看文件:
1 2 3 4 5 6
➜ cat word Twinkle, twinkle, little star How I wonder what you are Up above the world so high Like a diamond in the sky When the blazing sun is gone
替换:
1 2 3 4 5 6
➜ sed 's/little/big/' word Twinkle, twinkle, big star How I wonder what you are Up above the world so high Like a diamond in the sky When the blazing sun is gone
查看文本:
1 2 3 4 5 6 7
➜ cat word1 Oh if there's one thing to be taught it's dreams are made to be caught and friends can never be bought Doesn't matter how long it's been I know you'll always jump in 'Cause we don't know how to quit
全局替换:
1 2 3 4 5 6 7
➜ sed 's/to/can/g' word1 Oh if there's one thing can be taught it's dreams are made can be caught and friends can never be bought Doesn't matter how long it's been I know you'll always jump in 'Cause we don't know how can quit
按行替换(替换2到最后一行)
1 2 3 4 5 6 7
➜ sed '2,$s/to/can/' word1 Oh if there's one thing to be taught it's dreams are made can be caught and friends can never be bought Doesn't matter how long it's been I know you'll always jump in 'Cause we don't know how can quit
删除:
1 2 3 4 5
➜ sed '2d' word Twinkle, twinkle, little star Up above the world so high Like a diamond in the sky When the blazing sun is gone
显示行号:
1 2 3 4 5 6 7 8 9 10
➜ sed '=;2d' word 1 Twinkle, twinkle, little star 2 3 Up above the world so high 4 Like a diamond in the sky 5 When the blazing sun is gone
删除第2行到第四行:
1 2 3 4 5 6 7 8
➜ sed '=;2,4d' word 1 Twinkle, twinkle, little star 2 3 4 5 When the blazing sun is gone
向前插入:
1 2 3
➜ echo "hello" | sed 'i\kitty' kitty hello
向后插入:
1 2 3
➜ echo "kitty" | sed 'i\hello' hello kitty
替换第二行为hello kitty
1 2 3 4 5 6
➜ sed '2c\hello kitty' word Twinkle, twinkle, little star hello kitty Up above the world so high Like a diamond in the sky When the blazing sun is gone
替换第二行到最后一行为hello kitty
1 2 3
➜ sed '2,$c\hello kitty' word Twinkle, twinkle, little star hello kitty
写入行,把带star的行写入c文件中,c提前创建
1 2 3
➜ sed -n '/star/w c' word ➜ cat c Twinkle, twinkle, little star
退出:打印3行后,退出sed
1 2 3 4
➜ sed '3q' word Twinkle, twinkle, little star How I wonder what you are Up above the world so high
[root@iz2ze76ybn73dvwmdij06zz ~]## awk 'BEGIN {print "Name Math Chinese English History Sport grade\n----------------------------------------------"} {print $0}' students_store
Name Math Chinese English History Sport grade ---------------------------------------------------------- Xiaoka 60 80 40 90 77 class-1 Yizhihua 70 66 50 80 90 class-1 kerwin 80 90 60 70 60 class-2 Fengzheng 90 78 62 40 62 class-2
仅打印姓名、数学成绩、班级信息,再加一个文尾(再接再厉):
1 2 3 4 5 6 7 8 9
[root@iz2ze76ybn73dvwmdij06zz ~]## awk 'BEGIN {print "Name Math grade\n---------------------"} {print $1 2 "\t" $7} END {print "continue to exert oneself"}' students_store
Name Math grade --------------------- Xiaoka 60 class-1 Yizhihua 70 class-1 kerwin 80 class-2 Fengzheng 90 class-2 continue to exert oneself
环境变量是和shell紧密相关的,用户登录系统后就启动了一个shell,对于Linux来说一般是bash(Bourne Again shell,Bourne shell(sh)的扩展),也可以切换到其他版本的shell。bash有两个基本的系统级配置文件:/etc/bashrc和/etc/profile。这些配置文件包含了两组不同的变量:shell变量和环境变量。shell变量是局部的,而环境变量是全局的。环境变量是通过shell命令来设置。设置好的环境变量又可以被所以当前用户的程序使用。