一、二进制
1、原码、反码、补码
原码:[+1]原 = 0000 0001 [-1]原 = 1000 0001
(其中第一位是符号位,为1表示负,为0表示正)
==》
反码:正数的反码是其本身, 负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.
eg.[+1] = [00000001]原 = [00000001]反
[-1] = [10000001]原 = [11111110]反
补码:正数的补码依旧是其本身, 负数的补码是在反码的基础上+1。
eg. [+1] = [00000001]原 = [00000001]反 = [00000001]补
[-1] = [10000001]原 = [11111110]反 = [11111111]补
2、左/右移运算符<<、>>、 >>>
左移运算符 <<:将二进制字符串全部往左移若干位
右移运算符 >>:将二进制字符串全部往右移若干位
(二者移动后空出来的位以原符号位填充:正数高位补0,复数补1)
【注意】右移时,结果是往小里取整。比如说 - 25 >> 1 == -13,而不是 - 12。
无符号右移运算符>>>:左边空出来的位总以0填充
eg. Leetcode 191.位1的个数
/*
方法一:
假如 n==101100,那么n-1==101011,再运算n = n&n-1(同位置的两个数都是1为1,其余情况为0)
此时n会被更新为101000,而count++,那么循环往复就可以计算1的个数。
*/
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int count = 0;
while(n!=0){ //不能是n>0,因为“11111111111111111111111111111101”是负数
count++;
n &= (n-1);
}
return count;
}
}
/*
方法二:
因为 1 的二进制数除了最后一位,其余皆为0,若n & 1,那么假如n的二进制数末尾也是1,
那么n & 1==1,否则n & 1==0. 计算后,将 n 往右移一位,重复计算便可得到结果。
(且必须是无符号右移,否则当n为11111111111111111111111111111101时,
每次>>最左边都会得到1)
*/
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int count = 0;
do{
count += n & 1;
n >>>= 1;
}while(n>0);
return count;
}
}
3、异或 (例题–只出现一次的数–整数汉明距离)
- a ^ a == 0
- a ^ 0 == a
- a ^ b ^ c = c ^ a ^ b(可交换)
- 如果 a ^ b = c,那么有:a ^ c = b 、b ^ c = a。
(偶 + 偶 = 偶,偶 + 奇 = 奇,奇 + 奇 = 偶。对于这三条式子,偶即为0,奇即为1,+ 号即为 ^,刚好可以映证对于异或来说等式两边可以自由移动。)
(1)、Leetcode 136. 只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
【说明】:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
【示例 1】:
输入: [2,2,1]
输出: 1
【示例 2】:
输入: [4,1,2,1,2]
输出: 4
【方法一:先排序再遍历搜索】
//先排序再遍历搜索
class Solution {
public int singleNumber(int[] nums) {
Arrays.sort(nums); //快速排序--时间复杂度O(n^2)
for(int i=0; i<nums.length; i+=2){ //i两个两个跳
if(nums[i] != nums[i+1]) return nums[i];
}
return nums[nums.length-1]; //for循环没有遍历nums的最后一个元素
}
}
【方法二:借用异或的特点】
/*异或:相同为假、不同为真(二进制上:相同为0,不同为1)
故:a^a==0 a^0==a a^b^c==c^a^b
推出:尽管在一组数中,相同的数之间间隔其他数,但是终归在二进制异或时不影响结果,会互相抵消得0。
为什么满足交换律:
举个例子,假如现在有
0101
0100
1101
1101
0100
0101
1111
1110
1111
即: 0101 0100 1101 1111 各两个,和一个 1110。
现在将这9个二进制数的最右边的数抽离出来,可得:
1 0 1 1 0 1 1 0 1
异或后该位是: 1 0 1 1 0 1 1 0
类计1的个数: 1 1 2 3 3 4 5 5 6
可以看出:当该位 1 的累计个数为奇时,异或后该位是1(可以看成每2个相互抵消),而若为偶数则为0.
由此可见,其实无论上面的二进制数是谁先与谁异或都不影响该位的最终结果,结果只与该位的1的个数有关。
*/
class Solution {
public int singleNumber(int[] nums) {
int result = 0;
for(int i=0; i<nums.length; i++){
result ^= nums[i];
}
return result;
}
}
(2)、Leetcode 461. 汉明距离
【题意】
两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。
给出两个整数 x 和 y,计算它们之间的汉明距离。( 0 ≤ x, y < 2^31)
【示例】
输入: x = 1, y = 4
输出: 2
【解释】
1 (0 0 0 1)
4 (0 1 0 0)
↑ ↑
上面的箭头指出了对应二进制位不同的位置。
【我的做法】
class Solution {
public int hammingDistance(int x, int y) {
int cnt = 0;
for (int i = 1; i < 32; ++ i) {
if (isZeroOrOne(x, i) + isZeroOrOne(y, i) == 1)
++ cnt;
}
return cnt;
}
//如果数a的二进制数从右边数起第n位是 1 就返回 1,否则返回 0
public int isZeroOrOne(int a, int n) {
if ((a & (1 << n - 1)) > 0) return 1;
else return 0;
}
}
【官方题解—更优解—布赖恩·克尼根算法】
比如说,对于 111000100 和 101010100,异或之后得到了 010010000,那么其实我们要统计的也只是
那两个1而已,如果把 0 也判断一遍无疑是消耗了不必要的时间。
所以其实可以通过对 010010000 ^ (010010000 - 1) 得到 010000000,从而下一次判断1的时候,
会直接跳过中间部分的0.
class Solution {
public int hammingDistance(int x, int y) {
int xor = x ^ y;
int distance = 0;
while (xor != 0) {
distance += 1;
xor = xor & (xor - 1); //使得中间的0被直接跳过
}
return distance;
}
}
4、应用
(1) 不用 + - 计算两数之和
/*
因为二进制数只有0、1,异或又是相同为真、不同为假,对于不需要进位的数位,1会保存下来,
但是 1^1会为0,此时需要额外计算每一位是否有进位,再将进位的二进制数左移一位,就是每
一位应该加上进位值。
重复操作到进位值为0,则说明相加完毕。
eg. 4 + 6
4 : 0 1 0 0
6 : 0 1 1 0
4 ^ 6 : 0 0 1 0 ---还没加上进位时
4 & 6 : 0 1 0 0
再左移一位: 1 0 0 0 ---进位
将 0 0 1 0 和 1 0 0 0再异或可得 1 0 1 0
而此时进位为 0 0 0 0
说明已经不需要再加了
1 0 1 0就是最终结果
*/
class Solution {
public int getSum(int a, int b) {
int ans = a ^ b; // 此时的ans:还没有进位时各位的情况
a = (a & b) << 1; // & 可得出各位是否有进位,左移一位即是应该加上进位的数位
while (a != 0) { //进位不为0则要一直相加
ans = a ^ ans;
a = (a & ans) << 1;
}
return ans;
}
}
二、常用方法
1、快慢指针
===》 适用于可能会出现死循环的情况
eg. Leetcode 202.快乐数
/*
有可能会出现死循环:4, 16, 37, 58, 89, 145, 42, 20,4
但是快指针走的快,假如有快指针追上慢指针的那一刻,那么死循环实锤
*/
class Solution {
//求各位数上平方和
public int sum_of_squares(int n){
int result=0, d;
while(n>0){
d = n%10;
result += d*d;
n /= 10;
}
return result;
}
//快慢指针----适用于可能会出现循环的数据
public boolean isHappy(int n) {
int fast=n, later=n;
while(later!=1 && fast!=1){ //只要遇到1,即为快乐数
later = sum_of_squares(later);
fast = sum_of_squares(sum_of_squares(fast)); //快指针走两步
if(later==fast && fast!=1) return false; //不能是1==1,比如说n==10,那么later==fast==1
}
return true;
}
}
2、数学运算函数
(1)对数函数
log x (y) = log e (y) / log e (x) = Math.log(y) / Math.log(x);
3、字符串/char
(1)C++
①遍历字符串—查找
string 类将 npos 定义为保证大于任何有效下标的值
str.find(“哦”)==string::npos时则说明字符串str中不存在“哦”这个字符
反之,str.find(“哦”)!=string::npos则说明字符串str中存在“哦”这个字符
【例题】leetcode 345. 反转字符串中的元音字母
class Solution {
public:
string reverseVowels(string s) {
if (s.length() < 2) return s;
string str = "aeiouAEIOU";
int i = 0, j = s.length() - 1;
while (i < j) {
if (str.find(s[i]) != string::npos) { //左指针找到元音字母
while (str.find(s[j]) == string::npos && i < j) {
j --; //右指针找到元音字母则退出循环
}
if (i < j) {
//交换两个元音字符
char temp = s[i];
s[i] = s[j];
s[j] = temp;
j --;
}
}
i ++;
}
return s;
}
};
②常用方法
获得长度
size ( str );
数值转字符串
string str (to_string ( int a ) );
string str = to_string ( int a );
取字符串首尾字符的地址
string str = “67889”;
auto a = str.begin(); // a 为首字符地址
auto b = str.end() - 1; // b 为尾字符地址
用 *a 和 *b 即可表示字符的内容,即 *a = 6, *b = 9。
将 a + 1, 则 *a = 7。
//判断数字 x 是否为回文串
class Solution {
public:
bool isPalindrome(int x) {
string s(to_string(x)); //转换字符串
auto b=s.begin(),e=s.end()-1; //取地址
while (b != s.end() - 1){
if(*b != *e) return false; //比较内容
++ b;
-- e;
}
return true;
}
};
比较两个字符串是否相同
strcmp ( str1, str2) ;
char str[100];
scanf("%s", &str);
if (strcmp(str, "Rock") == 0 ) return 0; //如果输入Rock,则return。即str == Rock的话,为true。
③ 读入一行字符串
【方法一】:scanf ( ) 读入 char[ ]
使用方法:
char str[1024];
scanf("%[^\n]", &str); //遇到换行符停止输入
getchar(); //吸收换行符
【说明】
在scanf函数中,可以使用%c来读取一个字符,使用%s读取一个字符串, 但是读取字符串时不忽略空格,读字符串时忽略开始的空格,并且读到空格为止,因此只能读取一个单词,而不是整行字符串。
其实scanf函数也可完成这样的功能,而且还更强大。这里主要介绍一个参数,%[ ],这个参数的意义是读入一个字符集合。[ ]是个集合的标志,因此%[ ]特指读入此集合所限定的那些字符,比如%[A-Z]是输入大写字母,一旦遇到不在此集合的字符便停止。如果集合的第一个字符是“ ^ ”,这说明读取不在“ ^ ”后面集合的字符,即遇到" ^ "后面集合的字符便停止。注意此时读入的字符串是可以含有空格的,而且会把开头的空格也读进来。
注意:如果要循环的多次从屏幕上读取一行的话,就要在读取一行后,在用%c读取一个字符,将输入缓冲区中的换行符给读出来。否则的话,在下一次读取一行的时候,第一个就遇到’\n’,匹配不成功就直接返回了。这里可以用scanf()或者getchar()函数读取换行符。
方法二:getchar()读入char[]
使用方法:
char str[1024];
int i=0;
while((str[i]=getchar())!='\n') //一个个读,遇到换行符停止输入
i++;
getchar(); //吸收换行符
【说明】
这样一个一个读也可以,也会把开头的空格读进来。最后也需要考虑换行符,使用getchar()读出来。
【方法三】:gets()读入char[]
使用方法:
char str[1024];
gets(str);
【说明】
感觉这个就是多个getchar的集合函数,很好用。功能是从标准输入键盘上读入一个完整的行(从标准输入读,一直读到遇到换行符),把读到的内容存入括号中指定的字符数组里,并用空字符’\0’取代行尾的换行符’\n’。读入时不需要考虑换行符。
【方法四】:getline()读入string或char[]
使用方法:
string str;
getline(cin,str);//读入stringcharstr2[1024];
cin.getline(str2,1024);//读入char数组
【说明】
这是比较常用的方法,cin.getline第三个参数表示间隔符,默认为换行符’\n’。读入不需要考虑最后的换行符。
【方法五】:get()读入char[]
使用方法:
char str3[1024];
cin.get(str3,1024);//读入char数组
【说明】
get函数读入时需要考虑最后的换行符,也就是说,如果用get读入多行数据,要把’\n’另外读出来,一般使用cin.get(str,1024).get();来读入多组数据。
(2)Java
① char 和 String 互换
String str = “abcd”;
char[] ch = str.toCharArray();
char[] ch = {‘a’, ‘b’, ‘c’};
String str = new String(ch);
【插一道和转换关系不大的贪心】:
Leetcode 316. 去除重复字母
【题意】给你一个仅包含小写字母的字符串,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证返回结果的字典序最小(要求不能打乱其他字符的相对位置)。
【示例 1】:
输入: “bcabc”
输出: “abc”
【示例 2】:
输入: “cbacdcbc”
输出: “acdb”
/*
对于传进来的字符串,从左到右去判断每一个字符。
ans数组:存储最终结果,但是由于不一定26个字母都有出现,所以需要在return的时候重新开一个长度刚刚好的char数组minans转字符串。
str数组:将传进来的字符串转char数组
cnt数组:记录 a~z 26个字母剩余个数
use数组:记录 a~z 是否已经放在ans里面
思路:每打算将一个新出现的字符str[i]放进ans前,先在ans已经存有的字符里从左到右去判断有没有比str[i]大的字符a,
如果有再去判断在a到ans尾处这一段字符是否在str后续都还有存在,若是则将str[i]插在a的位置,若不是重复对ans的查询。
若都没有比str[i]大的a,那么将str[i]尾插入ans里。
*/
class Solution {
public String removeDuplicateLetters(String s) {
if (s.length() == 0) return s;
char[] ans = new char[26]; //初步答案
char[] str = s.toCharArray(); //目标字符
int[] cnt = new int[26]; //剩余数量
boolean[] use = new boolean[26];//是否已经被使用
Arrays.fill(use, false); //一开始都没被使用过
int k = 0, i; //k是ans的专门索引
for (i = 0; i < str.length; i++) {
cnt[str[i] - 'a'] ++; //记录初始个数
}
for (i = 0; i < str.length; i++) {
if (use[str[i] - 'a']) { //str[i]在ans里已经有了,但要记得cnt--
cnt[str[i] - 'a'] --;
continue;
}
int j;
boolean ok = true; //判断是插在ans尾部还是已经存在的字符
for (j = 0; j < k && ok; j ++) {
if (str[i] < ans[j]) { //ans里有比str[i]大的字符
int m;
for (m = j; m < k; m ++) {
if (cnt[ans[m] - 'a'] == 0) break; //有的字符的cnt已经==0了
}
if (m == k) { //是因为索引才跳出循环,说明这一部分字符都cnt>0
ok = false; //更新ok既能跳出for(防止后面也有满足的ans[j]),又能避开for下面的if
//更新各个方面的值
for (m = j; m < k; m ++) {
use[ans[m] - 'a'] = false;
}
ans[j] = str[i];
cnt[str[i] - 'a'] --;
use[ans[j] - 'a'] = true;
k = j+1;
}
}
}
//没找到就尾插入ans
if (ok) {
ans[k++] = str[i];
cnt[str[i] - 'a'] --;
use[str[i] - 'a'] = true;
}
}
//开一个长度合适的minans
char[] minans = new char[k];
for (i = 0; i < k; i++) {
minans[i] = ans[i];
}
return new String(minans);
}
}
② 动态字符串
String是不可变的对象, 因此在每次对String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,性能就会降低。
使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。所以多数情况下推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。
StringBuilder是5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。
【使用策略】:
基本原则:如果要操作少量的数据,用String ;单线程操作大量数据,用StringBuilder ;多线程操作大量数据,用StringBuffer。
不要使用String类的”+”来进行频繁的拼接,因为那样的性能极差的,应该使用StringBuffer或StringBuilder类,这在Java的优化上是一条比较重要的原则。
【常用方法】:
①String 转 StringBuilder:
StringBuilder sb = new StringBuilder(String str);
②StringBuilder 转 String:
String str = new StringBuilder(“abc”).toString();
③StringBuilder 插入字符:
StringBuilder sb = new StringBuilder(“abcd”);
sb.insert(int index, char val);
④StringBuilder在末尾添加元素
sb.append(tree.val);
⑤StringBuilder获取长度
int length() 返回内容的大小
⑥StringBuilder获取index位置的字符
char charAt(int index) 返回位置index处的字符;
例题可见:Leetcode 168. Excel表列名称(Java))
③ 实用字符串方法
//在fromIndex这个地方开始找第一个ch字符,找不到就返回-1
public int indexOf(int ch, int fromIndex)
//将一个值转换为字符串
String temp = String.valueOf(10); //将10转换成“10”
//字符串连接----大量操作时,比+连接节省时间
String temp = String.valueOf(1); //temp == "1"
temp = temp.concat("->"); //temp == "1->"
temp = temp.concat(String.valueOf(10); //temp == "1->10"
4、数组
(1)Java
①抽取数组的一部分
-------------Arrays.copyOfRange(int[ ] arr, int from, int to);
该方法需要导入java.util.Arrays;
同时,复制出新数组中,包含了原始数组的 arr [ from ],但不包含 arr [ to ],即是【from, to)的情况。
② 复制数组–指定区间
eg. char ch[ ] = Arrays.copyOfRange(ans, 0, k); //【0, k ]
5、幂次方
(1)投机取巧法(适用素数的幂次方)
直接拿数据范围内最大的幂次方对该数求模,若为0,则是幂次方,不为0则不是。
eg. 1162261467为int型范围内最大的3的幂次方,那么判断n是不是3的幂次方只需要判断 n > 0 && 1162261467 % n == 0 ,为true则是,为false则不是。
(2)& 运算 (与2相关)
对于判断是否为2的幂次方,可以用 n & (n - 1)== 0判断,若==0,则是,反之不是。
因为:对于任何一个2的幂次方,其二进制串只有一位是1,其余都为0,而将该数 - 1,原先二进制为1的位置变成0,右边的0又都变成1,两者一&,恒为0。
(3)巧用 % (部分合数)
比如判断是否是4的幂次方,其实可以把问题转换成判断是否为2的偶数次方。
而每一个2的偶数次方对3取模都 == 1 。比如说(2)^ (2n) = 4 * 4 ^ (n - 1) ,而对等式两边都对3取模,可得 [(2)^ (2n) ]% 3 = 1 * 4 ^ (n - 1) = 4 ^ (n - 1) , 以此类推,若一个数,是2的幂次方的同时,对3取模==1,则是偶数次方,即是4的幂次方。
因为2的奇数次方可以看成是偶数次方乘上1个2,求模后是2而不是1.
//leetcode 342. 4的幂
// 判断num是不是2的幂次方
class Solution {
public boolean isPowerOfFour(int num) {
// 特判 && 是2的幂次方 && 是2的偶数次方
return (num > 0) && ((num & (num - 1)) == 0) && (num % 3 == 1);
}
}
(4)借助01
对于上一个小点的4的幂次方,同样是转换成判断是否是2的偶数次方,利用二进制数的特点,我们可以得出:
2的幂次方,二进制只有一个位置是1(姑且将这个位置叫做c位),其余为0
2的奇数次方,二进制从右往左数,c位依次有第2、4、6、8、10、……
2的偶数次方,二进制从右往左数,c位依次有第1、3、5、7、9、……
假如将要判断的num & 1010101010……101010,则结果==0则是4的幂次方,反之则不是。同时这个 1010101010……101010可以写成0xaaaaaaaa(十六进制,a ==10,转换成2进制就是1010).
//leetcode 342. 4的幂
//判断num是不是4的幂次方
class Solution {
public boolean isPowerOfFour(int num) {
// 特判 && 2的幂次方 && 2的偶数次方
return (num > 0) && ((num & (num - 1)) == 0) && ((num & 0xaaaaaaaa) == 0);
}
}
6、sort
(1) vector
正序排序
sort(vec.begin(), vec.end());
逆序排序
sort(vec.rbegin(), vec.rend());
获得长度
int length = vec.size();
(2) 二维数组
//C++对二维数组正序排序
/*
sort(arr.begin(), arr.end(),
[](const vector<int>& x, const vector<int>& y){
return x[0] < y[0];
});
*/
7、vector容器
c.push_back(elem); 在容器最后位置添加一个元素elem
c.pop_back(); 删除容器最后位置处的元素
c.at(index); 返回指定index位置处的元素
8、A + B = B + A
当遇到一些类似的求相遇的点的时候,可以用 A + B = B + A 的思想寻找相遇点。
【例题】:Leetcode 剑指 Offer 52. 两个链表的第一个公共节点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
/*
思路:a走完走b的路,b走完走a的路,a + b = b + a,所以总会走到相逢的时候
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode a = headA, b = headB;
while (true) {
if (a == b) return a;
a = a == null ? headB : a.next;
b = b == null ? headA : b.next;
}
}
}
三、运行效率
1、计算多项式:
计算多项式在给定x处的值:
常规做法:分别计算每一项的值(计算了(n^2+n)/ 2 次乘法)
高效做法:提取公因式(只计算了n次乘法)
【代码】:
//计算多项式在给定点x处的值
#include<iostream>
#include<cmath>
using namespace std;
double f1(int n, double a[], double x);
double f2(int n, double a[], double x);
int main()
{
double a[11] = {1,2,3,4,5,6,7,8,9,10,11};
cout<<f1(10, a, 2)<<endl; //常规
cout<<f2(10, a, 2)<<endl; //高效
return 0;
}
double f1(int n, double a[], double x)
{
double result = a[0];
for(int i=1; i<=n; i++){
result += a[i]*pow(x,i);
}
return result;
}
double f2(int n, double a[], double x)
{
double result = a[n];
while(n--){
result = a[n] + x*result;
}
return result;
}
【参照下一小点内容计算实际运行时间】:
//计算多项式在给定点x处的值
#include<iostream>
#include<cmath>
#include<time.h>
using namespace std;
clock_t start, stop; //clock_t是clock()函数返回的变量类型
double duration; //记录被测函数运行时间,以秒为单位
#define Times 2e7 //重复计算次数
double f1(int n, double a[], double x);
double f2(int n, double a[], double x);
int main()
{
double a[11] = {1,2,3,4,5,6,7,8,9,10,11};
int i;
double ans1, ans2;
start = clock();
for(i=0; i<Times; i++) ans1 = f1(10,a,2); //重复调用以获得足够多的时钟打点数
stop = clock();
cout<<"常规做法:"<<endl<<endl;
cout<<"f(2)=="<<ans1<<endl;
duration = (double)((stop-start)/CLK_TCK/Times);
cout<<"所花费clock tick数是:"<<(double)(stop-start)<<endl;
cout<<"所花费秒数是:"<<duration<<endl<<endl<<endl;
start = clock();
for(i=0; i<Times; i++) ans2 = f2(10,a,2);
stop = clock();
duration = (double)((stop-start)/CLK_TCK/Times);
cout<<"高校做法:"<<endl<<endl;
cout<<"f(2)=="<<ans2<<endl;
cout<<"所花费clock tick数是:"<<(double)(stop-start)<<endl;
cout<<"所花费秒数是:"<<duration<<endl;
return 0;
}
double f1(int n, double a[], double x)
{
double result = a[0];
for(int i=1; i<=n; i++){
result += a[i]*pow(x,i);
}
return result;
}
double f2(int n, double a[], double x)
{
double result = a[n];
while(n--){
result = a[n] + x*result;
}
return result;
}
==》【结论】:两者之间在运行时间上差了一个数量级。
2、计算函数运行时间
clock() 方法:捕捉从程序开始运行到clock()被调用时所耗费的时间。
时间单位:clock tick ,即“时钟打点”,而不是秒。
常数CLK_TCK:机器时钟每秒所走的时钟打点数。(不同机器可能有所差异)
【模板】:
【注意】:当运行时间极小(小于1个clock tick)时,采用“重复”的方法计算出具体时间。即让函数或者程序重复运行多遍,使得总运行时间超过1个clock tick,再将 运行时间 / 运行次数,即可得到准确的单次运行时间。
(例子参照上一小点内容 三 1、计算多项式)
3、加减乘除
机器运算加减法的速度比乘除法快很多。
计算运行时间时,加减法可以忽略不计。
四、数据结构
1、哈希表
(1)Java
//有键值对的hash
//例题见 leetcode 105
HashMap<Integer, Integer> hash = new HashMap<Integer, Integer>();
hash.put(1, 3); //存入一对映射
hash.get(1); //获得关键字1对应的元素,此处可获得3
hash.containsKey(1); //查询是否有关键字1,返回的为boolean类型
hash.containsValue(3);//查询是否有值3,返回的也为boolean类型
/*
对传入的链表(头指针)进行判断是否有环形的情况,有则true,否则false。
*/
//记得在eclipse等编辑器时,需要导入包:
//import java.util.Set;
//import java.util.HashSet;
public class Solution {
public boolean hasCycle(ListNode head) {
Set<ListNode> table = new HashSet<>(); //创建一个哈希表
//contains():判断是否已存在的方法
//add():将未出现的内容加入哈希表中
while(head != null){
if(table.contains(head)) return true;
else table.add(head);
head = head.next;
}
return false;
}
}
(2)C++
unordered_map<string, int> book;
book["789"] = 1;
//判断“789”是否存在
book.count("789") != 0
book.find("789") != book.end()
2、栈
(1)Java
Leetcode 94. 二叉树的中序遍历
给定一个二叉树,返回它的中序 遍历。
【示例】:
输入: [1,null,2,3]
1
2
/
3
【输出】: [1,3,2]
【进阶】: 递归算法很简单,你可以通过迭代算法完成吗?
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/*
思路:在没遇到目标结点之前(目标结点在该题指没有左子树的结点,至于有没有右子树暂时不考量),
一直把每一步的左子树根节点放进栈里面,当遇到了目标节点,就把结点值输出,并将该结点弹出。
弹出后并不是直接去找栈顶的结点,而是刚刚弹出那个结点的右子树结点,哪怕它为空,也不能赋栈顶结点。
因为,直接赋予栈顶结点会导致在内层的while会重复操作,使得在左子树死循环。
反之,直接赋弹出结点的右子结点的思想是,在中序遍历中,本来应该是 左-中-右 的顺序,
但是当我们遍历到某个结点没有了左子树结点,就成了 “ 中 - 右 ” 的顺序,而此时,直接输出
“中” 位置的结点的值,转而去考虑 “右” 的情况,至于右空不空不重要,重要的是在第二层
while循环里面,不能重复把曾经被判定有左子树结点的结点放进来,不然会死循环。
而假如“右”不空,那么此时的“右”变成了另一个“左”的开始,反之,假如“右”为空,
就说明此时这个结点已经完成了它以及它包含的结点的所有的使命,此时就让栈弹出
更上一层结点给它去工作(此时同样注意不是拿这个结点去第二层while里面)。
==>> 笼统来讲,可以将栈理解成存“中”的地方,root是去找并存下“右”的起始点。
*/
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
//list.add(j);
List<Integer> list = new ArrayList<Integer>();
Stack<TreeNode> st = new Stack<>();
while (root != null || ! st.empty()) {
while (root != null) {
st.push(root);
root = root.left;
}
root = st.peek();
list.add(root.val);
st.pop();
root = root.right; //不能分类讨论,直接赋予右子树,哪怕为空
}
return list;
// 初始化Stack<TreeNode> st = new Stack<>();
// 判断是否为空 stack.empty()
// 取栈顶值(不出栈) stack.peek()
// 进栈 stack.push(Object);
// 出栈 stack.pop();
}
}
(2) C++
stack< int > s;
s.empty(); //如果栈为空则返回true, 否则返回false;
s.size(); //返回栈中元素的个数
s.top(); //返回栈顶元素, 但不删除该元素
s.pop(); //弹出栈顶元素, 但不返回其值
s.push(); //将元素压入栈顶
3、队列
(1)Java
Leetcode 102. 二叉树的层序遍历
【题目】给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
【示例】:
二叉树:[3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
/*
新建队列 Queue<Object> queue = new LinkedList<Object>();
常用方法:
抛出异常 返回特殊值
插入:add(e) offer(e) 插入一个元素
移除:remove() poll() 移除和返回队列的头
检查:element() peek() 返回但不移除队列的头。
返回长度: int length = queue.size();
判断是否为空:queue.isEmpty()
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> ans = new ArrayList<List<Integer>>();
Queue<TreeNode> queue = new LinkedList<TreeNode>();
if (root != null) queue.offer(root);
while (! queue.isEmpty()) {
int length = queue.size();
List<Integer> sub_ans = new ArrayList<Integer>();
while (length-- != 0) {
TreeNode temp = queue.peek();
sub_ans.add(temp.val);
if (temp.left != null) queue.offer(temp.left);
if (temp.right != null) queue.offer(temp.right);
queue.poll();
}
ans.add(sub_ans);
}
return ans;
}
}
(2) C++
//普通队列
queue<TreeNode*> q; //创建队列
q.push(root); //添加元素
q.empty(); //判空
int count = q.size(); //获得长度
TreeNode *temp = q.front(); //获得队首
q.pop(); //弹出队首
//双端队列
deque<int> deque1;
deque<int> deque2(deque1);
deque1.push_front(5); //头部插入数据
deque1.push_back(1); //尾部插入数据
deque1.front(); //查看队首
deque1.back(); //查看队尾
deque1.at(2); //查看某一位置的元素
deque1[2]; //查看某一位置的元素
deque1.size(); //返回长度
deque1.max_size(); //获得最大容纳量
deque1.pop_front(); //删除队首
deque1.pop_back(); //删除队尾
deque1.empty(); //判空
4、链表
List<String> ans = new ArrayList<String>();
ans.add( Object ob );
List<List<Integer>> ans = new ArrayList<List<Integer>>();
int length = ans.size();
int a = ans.get(0).get(0);
5、堆
priority_queue<int> Q; //创建一个优先队列(大顶堆)
Q.push(a); //将a放入堆里
Q.top(); //获得堆顶的值
Q.pop(); //弹出堆顶
五、输入输出
1、Java
(1)从txt读取数据并冒泡排序
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.Arrays;
public class Get_data{
//该方法用于读入txt中的数据
public static StringBuilder txt2String(File file){
StringBuilder result = new StringBuilder(); //动态字符串
try{ //异常处理
//构造一个BufferedReader来读取文件
BufferedReader br = new BufferedReader(new FileReader(file));
result.append(br.readLine()); //使用readLine方法,将一行数据(文档只有一行数据)读入
br.close();
}catch(Exception e){
e.printStackTrace();
}
return result;
}
public static void main(String[] args){
File file = new File("D:\\a-日常文档使用\\datafile.txt");//txt文本存放目录,根据自己的路径修改即可
String str = txt2String(file).toString(); //将txt的数据存储在str字符串中
//定义不同长度的数组
int[] ten = new int[10];
int[] bai = new int[100];
int[] wan = new int[10000];
int i,p=0,q=0,k=0, length = str.length();
//将字符串转变为数值:
while(q<length) {
char c = str.charAt(q); //charAt()方法:获得字符串的第q个字符
if(c == ',') { //如果遇到逗号,返回两个逗号之间的数字
//substring()方法:获得【p,q)区间的子字符串
//Integer.parseInt()方法:将字符串转变为十进制数
wan[k++] = Integer.parseInt(str.substring(p,q));
p = q+1; //更新p
}
q++;
}
wan[9999] = Integer.parseInt(str.substring(p,q-2)); //txt最后一个数需另外录入
for(k=0; k<10; k++) ten[k] = wan[k]; //十个和百个数的数组直接copy已经录入完毕的wan数组
for(k=0; k<100; k++) bai[k] = wan[k];
//对一万个数冒泡排序--正序排序
for(i=9999; i>=0; i--) {
for(int j=0; j<i; j++) {
if(wan[j]>wan[j+1]) { //发现前面的数比后面的数大,则交换两数
int temp = wan[j]; //temp临时变量
wan[j] = wan[j+1];
wan[j+1] = temp;
}
}
}
//测试是否排序正确
//for(i=0; i<10000; i++) System.out.println(wan[i]);
//快速排序
Arrays.sort(wan);
}
}
(2)format、浮点数、分隔符、进制
参考文献:https://blog.csdn.net/weixin_39590058/article/details/79875921
https://blog.csdn.net/pickpocket/article/details/84126477
https://www.runoob.com/w3cnote/java-printf-formate-demo.html
public class TestNumber
{
public static void main(String[] args)
{
String name = "盖伦";
int kill = 8;
String title = "超神";
String sentence = name + " 在进行了连续 " + kill + " 次击杀后,获得了 " + title + " 的称号 ";
//直接使用+进行字符串连接,编码感觉会比较繁琐,并且维护性差,易读性差
System.out.println(sentence);
String sentenceFormat = "%s 在进行了连续 %d 次击杀后, 获得了 %s 的称号%n";
//格式化输出,%s表示字符串,%d表示数字,%n表示换号
System.out.printf(sentenceFormat, name, kill, title);
System.out.format(sentenceFormat, name, kill, title);
//format和printf能够达到一模一样的效果
int year = 2020;
System.out.format("%d%n",year); //用%n或\n没差
//直接打印数字
System.out.printf("%8d%n",year); //用printf还是format没差
//总长度为8,默认右对齐
System.out.printf("%-8d%n",year);
//总长度为8,默认左对齐
System.out.printf("%08d%n",year);
//总长度为8,不够补0
System.out.printf("%,8d%n",year*10000);
//千位分隔符
System.out.format("%.2f%n",Math.PI);
//小数点位数
}
}
double d = 345.678;
String s = "hello!";
int i = 1234;
//"%"表示进行格式化输出,"%"之后的内容为格式的定义。
System.out.printf("%f",d);//"f"表示格式化输出浮点数。
System.out.printf("%9.2f",d);//"9.2"中的9表示输出的长度,2表示小数点后的位数。
System.out.printf("%+9.2f",d);//"+"表示输出的数带正负号。
System.out.printf("%-9.4f",d);//"-"表示输出的数左对齐(默认为右对齐)。
System.out.printf("%+-9.3f",d);//"+-"表示输出的数带正负号且左对齐。
System.out.printf("%d",i);//"d"表示输出十进制整数。
System.out.printf("%o",i);//"o"表示输出八进制整数。
System.out.printf("%x",i);//"d"表示输出十六进制整数。
System.out.printf("%#x",i);//"d"表示输出带有十六进制标志的整数。
System.out.printf("%s",s);//"d"表示输出字符串。
System.out.printf("输出一个浮点数:%f,一个整数:%d,一个字符串:%s",d,i,s);//可以输出多个变量,注意顺序。
System.out.printf("字符串:%2$s,%1$d的十六进制数:%1$#x",i,s);//"X$"表示第几个变量。
六、算法板块
1、数论
(1)数根:
将一个数的各个位数相加,直到新得到的数小于10为止。
比如说256,先将 2+5+6 = 13,但是13还是大于等于10,所以再计算一遍,1+3 = 4,此时 4 < 10,则256的数根为4。
【结论】:
原数: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
数根: 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 1 2 3
可以发现数根 9 个为一组, 1 - 9 循环出现。
===》将给定的数字减 1,相当于原数整体向左偏移了 1,然后再将得到的数字对 9 取余,最后将得到的结果加 1 即可。
【例题】:
(Leetcode 258. 各位相加)
给定一个非负整数 num,反复将各个位上的数字相加,直到结果为一位数。(示例):
输入: 38
输出: 2(解释): 各位相加的过程为:3 + 8 = 11, 1 + 1 = 2。 由于 2 是一位数,所以返回 2。
(进阶):
你可以不使用循环或者递归,且在 O(1) 时间复杂度内解决这个问题吗?
//常规做法
/*
class Solution {
public int addDigits(int num) {
while (num >= 10) {
num = findNext(num);
}
return num;
}
public int findNext(int num) { //求各位之和
int ans = 0;
while (num != 0) {
ans += num % 10;
num /= 10;
}
return ans;
}
}
*/
//数根: 根据数根规律,1,2,3,4,5,…… ,n-1,n的数根是1-9的循环
//但是诸如9,18,27等9的倍数,求模后是0,所以先把num-1向右移一位,求模后再+1
class Solution {
public int addDigits(int num) {
return (num - 1) % 9 + 1;
/*
if (num == 0) return 0;
if (num % 9 == 0) return 9;
return num % 9;
*/
}
}
【用途】:
1、数字根可作为一种检验计算正确性的方法。例如,两数字的和的数根等于两数字分别的数根的和。
2、另外,数根也可以用来判断数字的整除性,如果数根能被3或9整除,则原来的数也能被3或9整除。