===问:
给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。
案例:
s = “leetcode”
返回 0.s = “loveleetcode”
返回 2.
注意事项:您可以假定该字符串只包含小写字母。
===答:
方法一:
执行用时 :12 ms/16 ms, 击败了66.08%/62.57% 的用户
内存消耗 :5.8 MB, 击败了14.63%的用户
嵌套循环,依次遍历,在几个方法中并不是速度最慢的。
func firstUniqChar1(s string) int {
l := len(s) - 1
for i := 0; i <= l; i++ {
r := 1
for j := 0; j <= l; j++ {
if i != j && s[i] == s[j] {
r = 0
break
}
}
if r == 1 {
return i
}
}
return -1
}
方法二:
执行用时 :144 ms, 击败了5.19% 的用户
内存消耗 :5.8 MB, 击败了14.63%的用户
利用go的方法
strings.Count()
,遍历一次,计算每个字母出现的个数,
遍历过程中一旦某个字母只有唯一一个,立刻停止循环返回其索引号
func firstUniqChar2(s string) int {
for i := range s {
c := strings.Count(s, string(s[i]))
if c == 1 {
return i
}
}
return -1
}
方法三:
执行用时 :20 ms, 击败了60.58% 的用户
内存消耗 :5.7 MB, 击败了95.12%的用户
利用go的方法
strings.LastIndexByte()
,遍历当前元素与字符串中最后一个相同元素的索引号是否一致
如果相同,且是第一次检测,则说明是唯一元素,返回索引号
本方法需创建一个map,却消耗内存最低
func firstUniqChar3(s string) int {
v := make(map[int]int, 26)
for i := range s {
c := strings.LastIndexByte(s, s[i])
if c == i && v[c] != 1 {
return i
}
v[c] = 1
}
return -1
}
方法四:
执行用时 :40 ms/36 ms, 击败了46.73%/54.62% 的用户
内存消耗 :5.7 MB, 击败了95.12%的用户
第一次遍历字符串中每个元素,用map记录元素出现的次数,没有使用go自带的
strings.Count()
方法
第二次遍历字符串每个元素,当发现该元素在map中的值为1则意味着该元素只出现一次,立刻返回索引号
本方法与方法二思路相同,但内存占用和执行效率提升N倍
本方法统计了所有的元素的出现次数,而方法三只获得最后一个元素的位置,因此本方法执行效率慢了一倍。
func firstUniqChar4(s string) int {
r := make(map[rune]int, 26)
for _, v := range s {
r[v]++
}
for i, v := range s {
if r[v] == 1 {
return i
}
}
return -1
}
方法五:
执行用时 :8 ms,击败了88.08% 的用户
内存消耗 :5.8 MB, 击败了14.63%的用户
取巧的方法,因为题目里说了都是小写字母,否则数组
r
的长度无法确定
又因为都是小写字母,因此v-97
可得到0~26,正好用于数组索引
第一次遍历字符串计算元素出现次数
第二次遍历字符串遇到出现一次的元素返回其索引
方法二、方法四、方法五思路一致,都是统计元素出现次数,但方法五执行效率最高,方法四内存占用最少
推荐方法四,可以用于非小写字母的字符串统计,而方法五适用面太小
func firstUniqChar5(s string) int {
// ASCII数值:a-z:97-122,A-Z:65-90,0-9:48-57。
r := [26]int{}
for _, v := range s {
r[v-97]++
}
for i, v := range s {
if r[v-97] == 1 {
return i
}
}
return -1
}
方法六:
执行用时 :12 ms, 击败了66.15% 的用户
内存消耗 :6 MB, 击败了7.32%的用户
本方法是将方法三中的map改为了切片,但效果不明显,执行效率增加,但是内存占用也增加了~~
这是因为当s
为类似"aabbccddefggadfdaefdfaefaggjfghtrgregtrujuoiolokuhtrgrfe"
,在创建切片的时候就会创建一个同样长度的切片,否则如果使用append()
,每次更改内存地址,相比速度会减慢,没有必要了。
func firstUniqChar6(s string) int {
v := make([]int, len(s))
for i := range s {
c := strings.LastIndexByte(s, s[i])
if c == i && v[c] != 1 {
return i
}
v[c] = 1
}
return -1
}
方法七:
执行用时 :12 ms, 击败了66.15% 的用户
内存消耗 :5.8 MB, 击败了14.63%的用户
想让方法六中的切片固定为一个数组,看看会不会有所提升于是创建了方法七
果然内存消耗是降低了,但执行效率并没什么改变
方法三中的map可变长度又能自定义键名,反倒省了很多事
func firstUniqChar7(s string) int {
r := [26]int{}
for i, v := range s {
c := strings.LastIndexByte(s, s[i])
if c == i && r[v-97] != 1 {
return i
}
r[v-97] = 1
}
return -1
}
===拓展:
这里需要拓展的知识点有两个:
字母和数字的ASCII值:
我们常用的记住下面的就行了,实际使用的时候结合实际情况,发挥创意:
0~31及127(共33个)是控制字符或通信专用字符(其余为可显示字符),如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)、BEL(响铃)等;通信专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等;ASCII值为8、9、10
和13 分别转换为退格、制表、换行和回车字符。它们并没有特定的图形显示,但会依不同的应用程序,而对文本显示有不同的影响 [1] 。
32~126(共95个)是字符(32是空格),其中48~57为0到9十个阿拉伯数字。
65~90为26个大写英文字母,97~122号为26个小写英文字母,其余为一些标点符号、运算符号等。
go自带的strings统计及字符位置的几个函数
- func Contains(s, substr string) bool这个函数是查找某个字符是否在这个字符串中存在,存在返回true
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.Contains("widuu", "wi")) //true
fmt.Println(strings.Contains("wi", "widuu")) //false
}
- func ContainsAny(s, chars string) bool这个是查询字符串中是否包含多个字符
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.ContainsAny("widuu", "w&d")) //true
}
- func ContainsRune(s string, r rune) bool,这里边当然是字符串中是否包含rune类型,其中rune类型是utf8.RUneCountString可以完整表示全部Unicode字符的类型
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.ContainsRune("widuu", rune('w'))) //true
fmt.Println(strings.ContainsRune("widuu", 20)) //fasle
}
- func Count(s, sep string) int这个的作用就是输出,在一段字符串中有多少匹配到的字符
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.Count("widuu", "uu")) //1
fmt.Println(strings.Count("widuu", "u")) //2
}
- func Index(s, sep string) int 这个函数是查找字符串,然后返回当前的位置,输入的都是string类型,然后int的位置信息
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.Index("widuu", "i")) //1
fmt.Println(strings.Index("widuu", "u")) //3
}
- func IndexAny(s, chars string) int 这个函数是一样的查找,字符串第一次出现的位置,如果不存在就返回-1
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.IndexAny("widuu", "u")) //3
}
- func IndexByte(s string, c byte) int,这个函数功能还是查找第一次粗线的位置,只不过这次C是byte类型的,查找到返回位置,找不到返回-1
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.IndexByte("hello xiaowei", 'x')) //6
}
- func IndexRune(s string, r rune) int,还是查找位置,只不过这次是rune类型的
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.IndexRune("widuu", rune('w'))) //0
}
- func IndexFunc(s string, f func(rune) bool) int这个函数大家一看就知道了,是通过类型的转换来用函数查找位置,我们来代码看下哈
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.IndexFunc("nihaoma", split)) //3
}
func split(r rune) bool {
if r == 'a' {
return true
}
return false
}
- func LastIndex(s, sep string) int 看到这个大家可能也明白了查找的是最后出现的位置,正好跟index相反
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.LastIndex("widuu", "u")) // 4
}
- func LastIndexAny(s, chars string) int这个跟indexAny正好相反,也是查找最后一个
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.LastIndexAny("widuu", "u")) // 4
}