给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。
示例 1:
输入: num1 = "2", num2 = "3"
输出: "6"
示例 2:
输入: num1 = "123", num2 = "456"
输出: "56088"
说明:
num1 和 num2 的长度小于110。
num1 和 num2 只包含数字 0-9。
num1 和 num2 均不以零开头,除非是数字 0 本身。
不能使用任何标准库的大数类型(比如 BigInteger)或直接将输入转换为整数来处理。
原始思路:
1.将字符串化为数组 2.对应位相乘 3.将结果改为字符串
(对应位相乘没有思考清楚,即如何相乘,后文提到的补零)
思路更正:
法一:做加法
1.如果num 1和num2之一是0,则直接将0作为结果返回即可。
2.如果num 1和num2都不是0,通过竖式乘法计算。
从左往右遍历乘数,将乘数(num2)的每一位与被乘数(num1)相乘得到对应结果,再将每次得到的结果累加
num2处理最低位,其余的每一位的运算结果都需要补0
class Solution {
public:
string multiply(string num1, string num2) {
//为0的情况
if (num1 == "0" || num2 == "0") {
return "0";
}
string ans = "0";
int m = num1.size(), n = num2.size();
//从左向右遍历num2
for (int i = n - 1; i >= 0; i--) {
string curr;
int add = 0;//进位
for (int j = n - 1; j > i; j--) {
curr.push_back(0);//push_back() 在Vector最后添加一个元素(参数为要插入的值),此处为加入0
}
int y = num2.at(i) - '0';//string .at()用于获取指定字符;at(i),i就是想要获取的字符的下标,函数返回值为指定的字符
//遍历num1
for (int j = m - 1; j >= 0; j--) {
int x = num1.at(j) - '0';
int product = x * y + add;//相乘+进位
curr.push_back(product % 10);//保留余数
add = product / 10;//进位
}
while (add != 0) {
curr.push_back(add % 10);
add /= 10;
}
reverse(curr.begin(), curr.end());
// reverse函数用于反转在[first,last)范围内的顺序(包括first指向的元素,不包括last指向的元素),reverse函数没有返回值
for (auto &c : curr) {
c += '0';//补零
}
ans = addStrings(ans, curr);
}
return ans;
}
string addStrings(string &num1, string &num2) {
int i = num1.size() - 1, j = num2.size() - 1, add = 0;
string ans;
while (i >= 0 || j >= 0 || add != 0) {
int x = i >= 0 ? num1.at(i) - '0' : 0;
int y = j >= 0 ? num2.at(j) - '0' : 0;
int result = x + y + add;
ans.push_back(result % 10);
add = result / 10;
i--;
j--;
}
reverse(ans.begin(), ans.end());
for (auto &c: ans) {
c += '0';
}
return ans;
}
};// 8ms
两个函数: multiply(string num1, string num2);和addStrings(string &num1, string &num2) ;分别实现加法功能和进位功能
用到的特殊函数:
push_back() :在Vector最后添加一个元素(参数为要插入的值)。
string .at():获取指定字符;at(i), i就是想要获取的字符的下标,函数返回值为指定的字符
reverse():反转在[first,last)范围内的顺序(包括first指向的元素,不包括last指向的元素),reverse函数没有返回值
三目运算符:
法二:做乘法
用数组代替字符串存储结果
令m,n分别表示num1和num2的长度,并且均不为0,则他们乘积的长度为m+n-1或m+n
由此创建长度为m+n的数组ansArr用于存储乘积
num 1[i]×num2[j]的结果位于ansArr[i+j+1],如果ansArr[i+j+1]≥10,则将进位部分加到ansArr[i+j]
class Solution {
public:
string multiply(string num1, string num2) {
if (num1 == "0" || num2 == "0") {
return "0";
}
int m = num1.size(), n = num2.size();
auto ansArr = vector<int>(m + n);//存储乘积,大小为m+n
//遍历num1
for (int i = m - 1; i >= 0; i--) {
int x = num1.at(i) - '0';
//遍历num2
for (int j = n - 1; j >= 0; j--) {
int y = num2.at(j) - '0';
ansArr[i + j + 1] += x * y;//乘积
}
}
for (int i = m + n - 1; i > 0; i--) {
ansArr[i - 1] += ansArr[i] / 10;//十位进上(到前一位)
ansArr[i] %= 10;//余数存在该位
}
int index = ansArr[0] == 0 ? 1 : 0;
string ans;
while (index < m + n) {
ans.push_back(ansArr[index]);
index++;
}
for (auto &c: ans) {
c += '0';
}
return ans;
}
};//4ms
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母都恰好只用一次。
示例 1:
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
示例 2:
输入: strs = [""]
输出: [[""]]
示例 3:
输入: strs = ["a"]
输出: [["a"]]
提示:
1 <= strs.length <= 104
0 <= strs[i].length <= 100
strs[i] 仅包含小写字母
题目的意思是:将组成字母相同的单词放在一个数组中
重点:通过什么方法识别出每个单词的字母,首先明确字符串需要转化为数组
法一:排序
1.将不同的字符串转换为字符数组并按照字母顺序进行排序
2.异位词排序后的结果相同,故可以作为哈希表的key值
3.将字母异位词组成的集合作为哈希表的value值
java版本:
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
//判断是否为空字符串数组
if(strs == null || strs.length == 0){
return new ArrayList();
}
//1.创建一个哈希表
Map<String,List> map = new HashMap<String, List>();
for (String s: strs) {
//将字符串转化为字符数组
char[] chars = s.toCharArray();
//对字符数组按照字母顺序排序
Arrays.sort(chars);
//将排序后的字符串作为哈希表中的key值
String key = String.valueOf(chars);
//2.判读哈希表中是否有该key值
if (!map.containsKey(key)){
//若不存在,则为新的异位词语,在map中创建新的键值对
map.put(key,new ArrayList());
}
//3.将该字符串放在对应key的list中
map.get(key).add(s);
}
//返回map中所有键值对象构成的list
return new ArrayList(map.values());
}
}
c++版本
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string, vector<string>> mp;
//构造单词的字符排序作为键
for (string& str: strs) {
string key = str;
sort(key.begin(), key.end());//排序
mp[key].emplace_back(str);
}
vector<vector<string>> ans;
//加入散列表对应位置
for (auto it = mp.begin(); it != mp.end(); ++it) {
ans.emplace_back(it->second);
}
return ans;
}
};
法二:计数
由于互为字母异位词的两个字符串包含的字母相同,因此两个字符串中的相同字母出现的次数一定是相同的,故可以将每个字母出现的次数使用字符串表示,作为哈希表的键。
由于字符串只包含小写字母,因此对于每个字符串,可以使用长度为 26的数组记录每个字母出现的次数。
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
// 自定义对 array<int, 26> 类型的哈希函数
auto arrayHash = [fn = hash<int>{}] (const array<int, 26>& arr) -> size_t {
return accumulate(arr.begin(), arr.end(), 0u, [&](size_t acc, int num) {
return (acc << 1) ^ fn(num);
});
};
unordered_map<array<int, 26>, vector<string>, decltype(arrayHash)> mp(0, arrayHash);
for (string& str: strs) {
array<int, 26> counts{};
int length = str.length();
for (int i = 0; i < length; ++i) {
counts[str[i] - 'a'] ++;
}
mp[counts].emplace_back(str);
}
vector<vector<string>> ans;
for (auto it = mp.begin(); it != mp.end(); ++it) {
ans.emplace_back(it->second);
}
return ans;
}
};
法三:暴力求解
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
List<List<String>> result = new ArrayList<>();
int len = strs.length;
String[] strings = new String[len];
//将排序后的dest字符串数组放到一个新的字符串数组中
for (int i = 0; i < len; i++) {
byte[] bytes = strs[i].getBytes();
Arrays.sort(bytes);
strings[i] = new String(bytes);
}
//遍历新的字符串数组,将字符串相同的归类(已经归类的字符串,可以设置为null)
for (int i = 0; i < len; i++) {
ArrayList<String> strings1 = new ArrayList<>();
//如果为空说明已经归类
if (strings[i] != null) {
strings1.add(strs[i]);
//找其他排序后相同的字符串
for (int j = i + 1; j < len; j++) {
if (strings[i].equals(strings[j])) {
strings1.add(strs[j]);
// 已归类的字符串置为空
strings[j] = null;
}
}
}
//归类的字符串组放到结果集中
if (strings1.size() != 0)
result.add(strings1);
}
return result;
}
}
法四:哈希表解法
1.准备几个bucket,用于存放分组后的原字符串
2.根据排序后的字符串,找到所属的bucket,将原字符串放入bucket
排序后的字符串作为key,通过排序后的字符串findbucke
字符串数组遍历结束后,就完成了分组
3.将每个bucket中的子结果集放到最终的结果集中
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string, vector<string>> mp;
vector<vector<string>> result;//结果集
//排序;将对应将原字符串放入bucket
for(auto str : strs) {
auto tempStr = str;
sort(tempStr.begin(), tempStr.end());
mp[tempStr].push_back(str);
}
//放入结果集
for(auto elem : mp) {
result.push_back(elem.second);
}
return result;
}
};
总结:
一旦需要根据特征进行归类的,用散列表
这种分类题,思路其实可以确定下来:第一遍遍历就要分好类,第二遍取出来作为ans即可。
那么,key的选取思路才是核心。每一题都可能有每一题的特征。
对于本题而言,经过排序后,乱序的字符串可以统一分为一类
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string,vector<string>> mymap;
for( int i=0; i<strs.size() ;i++ )
{
string str = strs[i];
sort( str.begin() , str.end() );//排序
mymap[str].push_back( strs[i] );
}
vector<vector<string>> ans;//结果集
for(auto it = mymap.begin() ;it!= mymap.end() ;it++ )
{
ans.push_back(it->second);
}
return ans;
}
};